Skip to content

Mono-Doc

This file holds the entire API reference in a single page; should that be your preference.


Clients

Client

Bases: processors.AutoModEvents, processors.ChannelEvents, processors.GuildEvents, processors.MemberEvents, processors.MessageEvents, processors.ReactionEvents, processors.RoleEvents, processors.StageEvents, processors.ThreadEvents, processors.UserEvents, processors.VoiceEvents

The bot client.

Parameters:

Name Type Description Default
intents Union[int, Intents]

The intents to use

Intents.DEFAULT
default_prefix str | Iterable[str]

The default prefix (or prefixes) to use for prefixed commands. Defaults to your bot being mentioned.

MENTION_PREFIX
generate_prefixes Absent[Callable[..., Coroutine]]

A coroutine that returns a string or an iterable of strings to determine prefixes.

MISSING
status Status

The status the bot should log in with (IE ONLINE, DND, IDLE)

Status.ONLINE
activity Union[Activity, str]

The activity the bot should log in "playing"

None
sync_interactions bool

Should application commands be synced with discord?

True
delete_unused_application_cmds bool

Delete any commands from discord that aren't implemented in this client

False
enforce_interaction_perms bool

Enforce discord application command permissions, locally

True
fetch_members bool

Should the client fetch members from guilds upon startup (this will delay the client being ready)

False
auto_defer Absent[Union[AutoDefer, bool]]

A system to automatically defer commands after a set duration

MISSING
interaction_context Type[InteractionContext]

The object to instantiate for Interaction Context

InteractionContext
prefixed_context Type[PrefixedContext]

The object to instantiate for Prefixed Context

PrefixedContext
component_context Type[ComponentContext]

The object to instantiate for Component Context

ComponentContext
autocomplete_context Type[AutocompleteContext]

The object to instantiate for Autocomplete Context

AutocompleteContext
modal_context Type[ModalContext]

The object to instantiate for Modal Context

ModalContext
auto_defer Absent[Union[AutoDefer, bool]] MISSING
interaction_context Type[InteractionContext]

Type[InteractionContext]: InteractionContext: The object to instantiate for Interaction Context

InteractionContext
prefixed_context Type[PrefixedContext]

Type[PrefixedContext]: The object to instantiate for Prefixed Context

PrefixedContext
component_context Type[ComponentContext]

Type[ComponentContext]: The object to instantiate for Component Context

ComponentContext
autocomplete_context Type[AutocompleteContext]

Type[AutocompleteContext]: The object to instantiate for Autocomplete Context

AutocompleteContext
modal_context Type[ModalContext]

Type[ModalContext]: The object to instantiate for Modal Context

ModalContext
hybrid_context Type[HybridContext]

Type[HybridContext]: The object to instantiate for Hybrid Context

HybridContext
total_shards int

The total number of shards in use

1
shard_id int

The zero based int ID of this shard

0
debug_scope Absent[Snowflake_Type]

Force all application commands to be registered within this scope

MISSING
basic_logging bool

Utilise basic logging to output library data to console. Do not use in combination with Client.logger

False
logging_level int

The level of logging to use for basic_logging. Do not use in combination with Client.logger

logging.INFO
logger logging.Logger

The logger NAFF should use. Do not use in combination with Client.basic_logging and Client.logging_level. Note: Different loggers with multiple clients are not supported

logger

Optionally, you can configure the caches here, by specifying the name of the cache, followed by a dict-style object to use. It is recommended to use smart_cache.create_cache to configure the cache here. as an example, this is a recommended attribute message_cache=create_cache(250, 50),

Intents Note

By default, all non-privileged intents will be enabled

Caching Note

Setting a message cache hard limit to None is not recommended, as it could result in extremely high memory usage, we suggest a sane limit.

Source code in naff/client/client.py
 185
 186
 187
 188
 189
 190
 191
 192
 193
 194
 195
 196
 197
 198
 199
 200
 201
 202
 203
 204
 205
 206
 207
 208
 209
 210
 211
 212
 213
 214
 215
 216
 217
 218
 219
 220
 221
 222
 223
 224
 225
 226
 227
 228
 229
 230
 231
 232
 233
 234
 235
 236
 237
 238
 239
 240
 241
 242
 243
 244
 245
 246
 247
 248
 249
 250
 251
 252
 253
 254
 255
 256
 257
 258
 259
 260
 261
 262
 263
 264
 265
 266
 267
 268
 269
 270
 271
 272
 273
 274
 275
 276
 277
 278
 279
 280
 281
 282
 283
 284
 285
 286
 287
 288
 289
 290
 291
 292
 293
 294
 295
 296
 297
 298
 299
 300
 301
 302
 303
 304
 305
 306
 307
 308
 309
 310
 311
 312
 313
 314
 315
 316
 317
 318
 319
 320
 321
 322
 323
 324
 325
 326
 327
 328
 329
 330
 331
 332
 333
 334
 335
 336
 337
 338
 339
 340
 341
 342
 343
 344
 345
 346
 347
 348
 349
 350
 351
 352
 353
 354
 355
 356
 357
 358
 359
 360
 361
 362
 363
 364
 365
 366
 367
 368
 369
 370
 371
 372
 373
 374
 375
 376
 377
 378
 379
 380
 381
 382
 383
 384
 385
 386
 387
 388
 389
 390
 391
 392
 393
 394
 395
 396
 397
 398
 399
 400
 401
 402
 403
 404
 405
 406
 407
 408
 409
 410
 411
 412
 413
 414
 415
 416
 417
 418
 419
 420
 421
 422
 423
 424
 425
 426
 427
 428
 429
 430
 431
 432
 433
 434
 435
 436
 437
 438
 439
 440
 441
 442
 443
 444
 445
 446
 447
 448
 449
 450
 451
 452
 453
 454
 455
 456
 457
 458
 459
 460
 461
 462
 463
 464
 465
 466
 467
 468
 469
 470
 471
 472
 473
 474
 475
 476
 477
 478
 479
 480
 481
 482
 483
 484
 485
 486
 487
 488
 489
 490
 491
 492
 493
 494
 495
 496
 497
 498
 499
 500
 501
 502
 503
 504
 505
 506
 507
 508
 509
 510
 511
 512
 513
 514
 515
 516
 517
 518
 519
 520
 521
 522
 523
 524
 525
 526
 527
 528
 529
 530
 531
 532
 533
 534
 535
 536
 537
 538
 539
 540
 541
 542
 543
 544
 545
 546
 547
 548
 549
 550
 551
 552
 553
 554
 555
 556
 557
 558
 559
 560
 561
 562
 563
 564
 565
 566
 567
 568
 569
 570
 571
 572
 573
 574
 575
 576
 577
 578
 579
 580
 581
 582
 583
 584
 585
 586
 587
 588
 589
 590
 591
 592
 593
 594
 595
 596
 597
 598
 599
 600
 601
 602
 603
 604
 605
 606
 607
 608
 609
 610
 611
 612
 613
 614
 615
 616
 617
 618
 619
 620
 621
 622
 623
 624
 625
 626
 627
 628
 629
 630
 631
 632
 633
 634
 635
 636
 637
 638
 639
 640
 641
 642
 643
 644
 645
 646
 647
 648
 649
 650
 651
 652
 653
 654
 655
 656
 657
 658
 659
 660
 661
 662
 663
 664
 665
 666
 667
 668
 669
 670
 671
 672
 673
 674
 675
 676
 677
 678
 679
 680
 681
 682
 683
 684
 685
 686
 687
 688
 689
 690
 691
 692
 693
 694
 695
 696
 697
 698
 699
 700
 701
 702
 703
 704
 705
 706
 707
 708
 709
 710
 711
 712
 713
 714
 715
 716
 717
 718
 719
 720
 721
 722
 723
 724
 725
 726
 727
 728
 729
 730
 731
 732
 733
 734
 735
 736
 737
 738
 739
 740
 741
 742
 743
 744
 745
 746
 747
 748
 749
 750
 751
 752
 753
 754
 755
 756
 757
 758
 759
 760
 761
 762
 763
 764
 765
 766
 767
 768
 769
 770
 771
 772
 773
 774
 775
 776
 777
 778
 779
 780
 781
 782
 783
 784
 785
 786
 787
 788
 789
 790
 791
 792
 793
 794
 795
 796
 797
 798
 799
 800
 801
 802
 803
 804
 805
 806
 807
 808
 809
 810
 811
 812
 813
 814
 815
 816
 817
 818
 819
 820
 821
 822
 823
 824
 825
 826
 827
 828
 829
 830
 831
 832
 833
 834
 835
 836
 837
 838
 839
 840
 841
 842
 843
 844
 845
 846
 847
 848
 849
 850
 851
 852
 853
 854
 855
 856
 857
 858
 859
 860
 861
 862
 863
 864
 865
 866
 867
 868
 869
 870
 871
 872
 873
 874
 875
 876
 877
 878
 879
 880
 881
 882
 883
 884
 885
 886
 887
 888
 889
 890
 891
 892
 893
 894
 895
 896
 897
 898
 899
 900
 901
 902
 903
 904
 905
 906
 907
 908
 909
 910
 911
 912
 913
 914
 915
 916
 917
 918
 919
 920
 921
 922
 923
 924
 925
 926
 927
 928
 929
 930
 931
 932
 933
 934
 935
 936
 937
 938
 939
 940
 941
 942
 943
 944
 945
 946
 947
 948
 949
 950
 951
 952
 953
 954
 955
 956
 957
 958
 959
 960
 961
 962
 963
 964
 965
 966
 967
 968
 969
 970
 971
 972
 973
 974
 975
 976
 977
 978
 979
 980
 981
 982
 983
 984
 985
 986
 987
 988
 989
 990
 991
 992
 993
 994
 995
 996
 997
 998
 999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
1631
1632
1633
1634
1635
1636
1637
1638
1639
1640
1641
1642
1643
1644
1645
1646
1647
1648
1649
1650
1651
1652
1653
1654
1655
1656
1657
1658
1659
1660
1661
1662
1663
1664
1665
1666
1667
1668
1669
1670
1671
1672
1673
1674
1675
1676
1677
1678
1679
1680
1681
1682
1683
1684
1685
1686
1687
1688
1689
1690
1691
1692
1693
1694
1695
1696
1697
1698
1699
1700
1701
1702
1703
1704
1705
1706
1707
1708
1709
1710
1711
1712
1713
1714
1715
1716
1717
1718
1719
1720
1721
1722
1723
1724
1725
1726
1727
1728
1729
1730
1731
1732
1733
1734
1735
1736
1737
1738
1739
1740
1741
1742
1743
1744
1745
1746
1747
1748
1749
1750
1751
1752
1753
1754
1755
1756
1757
1758
1759
1760
1761
1762
1763
1764
1765
1766
1767
1768
1769
1770
1771
1772
1773
1774
1775
1776
1777
1778
1779
1780
1781
1782
1783
1784
1785
1786
1787
1788
1789
1790
1791
1792
1793
1794
1795
1796
1797
1798
1799
1800
1801
1802
1803
1804
1805
1806
1807
1808
1809
1810
1811
1812
1813
1814
1815
1816
1817
1818
1819
1820
1821
1822
1823
1824
1825
1826
1827
1828
1829
1830
1831
1832
1833
1834
1835
1836
1837
1838
1839
1840
1841
1842
1843
1844
1845
1846
1847
1848
1849
1850
1851
1852
1853
1854
1855
1856
1857
1858
1859
1860
1861
1862
1863
1864
1865
1866
1867
1868
1869
1870
1871
1872
1873
1874
1875
1876
1877
1878
1879
1880
1881
1882
1883
1884
1885
1886
1887
1888
1889
1890
1891
1892
1893
1894
1895
1896
1897
1898
1899
1900
1901
1902
1903
1904
1905
1906
1907
1908
1909
1910
1911
1912
1913
1914
1915
1916
1917
1918
1919
1920
1921
1922
1923
1924
1925
1926
1927
1928
1929
1930
1931
1932
1933
1934
1935
1936
1937
1938
1939
1940
1941
1942
1943
1944
1945
1946
1947
1948
1949
1950
1951
1952
1953
1954
1955
1956
1957
1958
1959
1960
1961
1962
1963
1964
1965
1966
1967
1968
1969
1970
1971
1972
1973
1974
1975
1976
1977
1978
1979
1980
1981
1982
1983
1984
1985
1986
1987
1988
1989
1990
1991
1992
1993
1994
1995
1996
1997
1998
1999
2000
2001
2002
2003
2004
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
2027
2028
2029
2030
2031
2032
2033
2034
2035
2036
2037
2038
2039
2040
2041
2042
2043
2044
2045
2046
2047
2048
2049
2050
2051
2052
2053
2054
2055
2056
2057
2058
2059
2060
2061
2062
2063
2064
2065
2066
2067
2068
2069
2070
2071
2072
2073
2074
2075
2076
2077
2078
2079
2080
2081
2082
2083
2084
2085
2086
2087
2088
2089
2090
2091
2092
2093
2094
2095
2096
2097
2098
2099
2100
2101
2102
2103
2104
2105
2106
2107
2108
2109
2110
2111
2112
2113
2114
2115
2116
2117
2118
2119
2120
2121
2122
2123
2124
2125
2126
2127
2128
2129
2130
2131
2132
2133
2134
2135
2136
2137
2138
2139
2140
2141
2142
2143
2144
2145
2146
2147
2148
2149
2150
2151
2152
2153
2154
2155
2156
2157
2158
2159
2160
2161
2162
2163
2164
2165
2166
2167
2168
2169
2170
2171
2172
2173
2174
2175
2176
2177
2178
2179
2180
2181
2182
2183
2184
2185
2186
2187
2188
2189
2190
2191
2192
2193
2194
2195
2196
2197
2198
2199
2200
2201
class Client(
    processors.AutoModEvents,
    processors.ChannelEvents,
    processors.GuildEvents,
    processors.MemberEvents,
    processors.MessageEvents,
    processors.ReactionEvents,
    processors.RoleEvents,
    processors.StageEvents,
    processors.ThreadEvents,
    processors.UserEvents,
    processors.VoiceEvents,
):
    """

    The bot client.

    Args:
        intents: The intents to use

        default_prefix: The default prefix (or prefixes) to use for prefixed commands. Defaults to your bot being mentioned.
        generate_prefixes: A coroutine that returns a string or an iterable of strings to determine prefixes.
        status: The status the bot should log in with (IE ONLINE, DND, IDLE)
        activity: The activity the bot should log in "playing"

        sync_interactions: Should application commands be synced with discord?
        delete_unused_application_cmds: Delete any commands from discord that aren't implemented in this client
        enforce_interaction_perms: Enforce discord application command permissions, locally
        fetch_members: Should the client fetch members from guilds upon startup (this will delay the client being ready)

        auto_defer: A system to automatically defer commands after a set duration
        interaction_context: The object to instantiate for Interaction Context
        prefixed_context: The object to instantiate for Prefixed Context
        component_context: The object to instantiate for Component Context
        autocomplete_context: The object to instantiate for Autocomplete Context
        modal_context: The object to instantiate for Modal Context

        auto_defer: AutoDefer: A system to automatically defer commands after a set duration
        interaction_context: Type[InteractionContext]: InteractionContext: The object to instantiate for Interaction Context
        prefixed_context: Type[PrefixedContext]: The object to instantiate for Prefixed Context
        component_context: Type[ComponentContext]: The object to instantiate for Component Context
        autocomplete_context: Type[AutocompleteContext]: The object to instantiate for Autocomplete Context
        modal_context: Type[ModalContext]: The object to instantiate for Modal Context
        hybrid_context: Type[HybridContext]: The object to instantiate for Hybrid Context

        total_shards: The total number of shards in use
        shard_id: The zero based int ID of this shard

        debug_scope: Force all application commands to be registered within this scope
        basic_logging: Utilise basic logging to output library data to console. Do not use in combination with `Client.logger`
        logging_level: The level of logging to use for basic_logging. Do not use in combination with `Client.logger`
        logger: The logger NAFF should use. Do not use in combination with `Client.basic_logging` and `Client.logging_level`. Note: Different loggers with multiple clients are not supported

    Optionally, you can configure the caches here, by specifying the name of the cache, followed by a dict-style object to use.
    It is recommended to use `smart_cache.create_cache` to configure the cache here.
    as an example, this is a recommended attribute `message_cache=create_cache(250, 50)`,

    ???+ note "Intents Note"
        By default, all non-privileged intents will be enabled

    ???+ note "Caching Note"
        Setting a message cache hard limit to None is not recommended, as it could result in extremely high memory usage, we suggest a sane limit.


    """

    def __init__(
        self,
        *,
        activity: Union[Activity, str] = None,
        auto_defer: Absent[Union[AutoDefer, bool]] = MISSING,
        autocomplete_context: Type[AutocompleteContext] = AutocompleteContext,
        component_context: Type[ComponentContext] = ComponentContext,
        debug_scope: Absent["Snowflake_Type"] = MISSING,
        default_prefix: str | Iterable[str] = MENTION_PREFIX,
        delete_unused_application_cmds: bool = False,
        enforce_interaction_perms: bool = True,
        fetch_members: bool = False,
        generate_prefixes: Absent[Callable[..., Coroutine]] = MISSING,
        global_post_run_callback: Absent[Callable[..., Coroutine]] = MISSING,
        global_pre_run_callback: Absent[Callable[..., Coroutine]] = MISSING,
        intents: Union[int, Intents] = Intents.DEFAULT,
        interaction_context: Type[InteractionContext] = InteractionContext,
        logger: logging.Logger = logger,
        owner_ids: Iterable["Snowflake_Type"] = (),
        modal_context: Type[ModalContext] = ModalContext,
        prefixed_context: Type[PrefixedContext] = PrefixedContext,
        hybrid_context: Type[HybridContext] = HybridContext,
        send_command_tracebacks: bool = True,
        shard_id: int = 0,
        status: Status = Status.ONLINE,
        sync_interactions: bool = True,
        sync_ext: bool = True,
        total_shards: int = 1,
        basic_logging: bool = False,
        logging_level: int = logging.INFO,
        **kwargs,
    ) -> None:
        if basic_logging:
            logging.basicConfig()
            logger.setLevel(logging_level)

        # Set Up logger and overwrite the constant
        self.logger = logger
        """The logger NAFF should use. Do not use in combination with `Client.basic_logging` and `Client.logging_level`.
        !!! note
            Different loggers with multiple clients are not supported"""
        constants.logger = logger

        # Configuration
        self.sync_interactions: bool = sync_interactions
        """Should application commands be synced"""
        self.del_unused_app_cmd: bool = delete_unused_application_cmds
        """Should unused application commands be deleted?"""
        self.sync_ext: bool = sync_ext
        """Should we sync whenever a extension is (un)loaded"""
        self.debug_scope = to_snowflake(debug_scope) if debug_scope is not MISSING else MISSING
        """Sync global commands as guild for quicker command updates during debug"""
        self.default_prefix = default_prefix
        """The default prefix to be used for prefixed commands"""
        self.generate_prefixes = generate_prefixes if generate_prefixes is not MISSING else self.generate_prefixes
        """A coroutine that returns a prefix or an iterable of prefixes, for dynamic prefixes"""
        self.send_command_tracebacks: bool = send_command_tracebacks
        """Should the traceback of command errors be sent in reply to the command invocation"""
        if auto_defer is True:
            auto_defer = AutoDefer(enabled=True)
        else:
            auto_defer = auto_defer or AutoDefer()
        self.auto_defer = auto_defer
        """A system to automatically defer commands after a set duration"""
        self.intents = intents if isinstance(intents, Intents) else Intents(intents)

        # resources

        self.http: HTTPClient = HTTPClient()
        """The HTTP client to use when interacting with discord endpoints"""

        # context objects
        self.interaction_context: Type[InteractionContext] = interaction_context
        """The object to instantiate for Interaction Context"""
        self.prefixed_context: Type[PrefixedContext] = prefixed_context
        """The object to instantiate for Prefixed Context"""
        self.component_context: Type[ComponentContext] = component_context
        """The object to instantiate for Component Context"""
        self.autocomplete_context: Type[AutocompleteContext] = autocomplete_context
        """The object to instantiate for Autocomplete Context"""
        self.modal_context: Type[ModalContext] = modal_context
        """The object to instantiate for Modal Context"""
        self.hybrid_context: Type[HybridContext] = hybrid_context
        """The object to instantiate for Hybrid Context"""

        # flags
        self._ready = asyncio.Event()
        self._closed = False
        self._startup = False

        self._guild_event = asyncio.Event()
        self.guild_event_timeout = 3
        """How long to wait for guilds to be cached"""

        # Sharding
        self.total_shards = total_shards
        self._connection_state: ConnectionState = ConnectionState(self, intents, shard_id)

        self.enforce_interaction_perms = enforce_interaction_perms

        self.fetch_members = fetch_members
        """Fetch the full members list of all guilds on startup"""

        self._mention_reg = MISSING

        # caches
        self.cache: GlobalCache = GlobalCache(self, **{k: v for k, v in kwargs.items() if hasattr(GlobalCache, k)})
        # these store the last sent presence data for change_presence
        self._status: Status = status
        if isinstance(activity, str):
            self._activity = Activity.create(name=str(activity))
        else:
            self._activity: Activity = activity

        self._user: Absent[NaffUser] = MISSING
        self._app: Absent[Application] = MISSING

        # collections
        self.prefixed_commands: Dict[str, PrefixedCommand] = {}
        """A dictionary of registered prefixed commands: `{name: command}`"""
        self.interactions: Dict["Snowflake_Type", Dict[str, InteractionCommand]] = {}
        """A dictionary of registered application commands: `{cmd_id: command}`"""
        self._component_callbacks: Dict[str, Callable[..., Coroutine]] = {}
        self._modal_callbacks: Dict[str, Callable[..., Coroutine]] = {}
        self._interaction_scopes: Dict["Snowflake_Type", "Snowflake_Type"] = {}
        self.processors: Dict[str, Callable[..., Coroutine]] = {}
        self.__modules = {}
        self.ext = {}
        """A dictionary of mounted ext"""
        self.listeners: Dict[str, List] = {}
        self.waits: Dict[str, List] = {}
        self.owner_ids: set[Snowflake_Type] = set(owner_ids)

        self.async_startup_tasks: list[Coroutine] = []
        """A list of coroutines to run during startup"""

        # callbacks
        if global_pre_run_callback:
            if asyncio.iscoroutinefunction(global_pre_run_callback):
                self.pre_run_callback: Callable[..., Coroutine] = global_pre_run_callback
            else:
                raise TypeError("Callback must be a coroutine")
        else:
            self.pre_run_callback = MISSING

        if global_post_run_callback:
            if asyncio.iscoroutinefunction(global_post_run_callback):
                self.post_run_callback: Callable[..., Coroutine] = global_post_run_callback
            else:
                raise TypeError("Callback must be a coroutine")
        else:
            self.post_run_callback = MISSING

        super().__init__()
        self._sanity_check()

    @property
    def is_closed(self) -> bool:
        """Returns True if the bot has closed."""
        return self._closed

    @property
    def is_ready(self) -> bool:
        """Returns True if the bot is ready."""
        return self._ready.is_set()

    @property
    def latency(self) -> float:
        """Returns the latency of the websocket connection."""
        return self._connection_state.latency

    @property
    def average_latency(self) -> float:
        """Returns the average latency of the websocket connection."""
        return self._connection_state.average_latency

    @property
    def start_time(self) -> datetime:
        """The start time of the bot."""
        return self._connection_state.start_time

    @property
    def gateway_started(self) -> bool:
        """Returns if the gateway has been started."""
        return self._connection_state.gateway_started.is_set()

    @property
    def user(self) -> NaffUser:
        """Returns the bot's user."""
        return self._user

    @property
    def app(self) -> Application:
        """Returns the bots application."""
        return self._app

    @property
    def owner(self) -> Optional["User"]:
        """Returns the bot's owner'."""
        try:
            return self.app.owner
        except TypeError:
            return MISSING

    @property
    def owners(self) -> List["User"]:
        """Returns the bot's owners as declared via `client.owner_ids`."""
        return [self.get_user(u_id) for u_id in self.owner_ids]

    @property
    def guilds(self) -> List["Guild"]:
        """Returns a list of all guilds the bot is in."""
        return self.user.guilds

    @property
    def status(self) -> Status:
        """
        Get the status of the bot.

        IE online, afk, dnd

        """
        return self._status

    @property
    def activity(self) -> Activity:
        """Get the activity of the bot."""
        return self._activity

    @property
    def application_commands(self) -> List[InteractionCommand]:
        """A list of all application commands registered within the bot."""
        commands = []
        for scope in self.interactions.keys():
            commands += [cmd for cmd in self.interactions[scope].values() if cmd not in commands]

        return commands

    @property
    def ws(self) -> GatewayClient:
        """Returns the websocket client."""
        return self._connection_state.gateway

    def get_guild_websocket(self, id: "Snowflake_Type") -> GatewayClient:
        return self.ws

    def _sanity_check(self) -> None:
        """Checks for possible and common errors in the bot's configuration."""
        logger.debug("Running client sanity checks...")
        contexts = {
            self.interaction_context: InteractionContext,
            self.prefixed_context: PrefixedContext,
            self.component_context: ComponentContext,
            self.autocomplete_context: AutocompleteContext,
            self.modal_context: ModalContext,
            self.hybrid_context: HybridContext,
        }
        for obj, expected in contexts.items():
            if not issubclass(obj, expected):
                raise TypeError(f"{obj.__name__} must inherit from {expected.__name__}")

        if self.del_unused_app_cmd:
            logger.warning(
                "As `delete_unused_application_cmds` is enabled, the client must cache all guilds app-commands, this could take a while."
            )

        if Intents.GUILDS not in self._connection_state.intents:
            logger.warning("GUILD intent has not been enabled; this is very likely to cause errors")

        if self.fetch_members and Intents.GUILD_MEMBERS not in self._connection_state.intents:
            raise BotException("Members Intent must be enabled in order to use fetch members")
        elif self.fetch_members:
            logger.warning("fetch_members enabled; startup will be delayed")

        if len(self.processors) == 0:
            logger.warning("No Processors are loaded! This means no events will be processed!")

    async def generate_prefixes(self, bot: "Client", message: Message) -> str | Iterable[str]:
        """
        A method to get the bot's default_prefix, can be overridden to add dynamic prefixes.

        !!! note
            To easily override this method, simply use the `generate_prefixes` parameter when instantiating the client

        Args:
            bot: A reference to the client
            message: A message to determine the prefix from.

        Example:
            ```python
            async def generate_prefixes(bot, message):
                if message.guild.id == 870046872864165888:
                    return ["!"]
                return bot.default_prefix

            bot = Client(generate_prefixes=generate_prefixes, ...)
            ```

        Returns:
            A string or an iterable of strings to use as a prefix. By default, this will return `client.default_prefix`

        """
        return self.default_prefix

    def _queue_task(self, coro: Listener, event: BaseEvent, *args, **kwargs) -> asyncio.Task:
        async def _async_wrap(_coro: Listener, _event: BaseEvent, *_args, **_kwargs) -> None:
            try:
                if len(_event.__attrs_attrs__) == 2:
                    # override_name & bot
                    await _coro()
                else:
                    await _coro(_event, *_args, **_kwargs)
            except asyncio.CancelledError:
                pass
            except Exception as e:
                if isinstance(event, events.Error):
                    # No infinite loops please
                    self.default_error_handler(repr(event), e)
                else:
                    self.dispatch(events.Error(repr(event), e))

        wrapped = _async_wrap(coro, event, *args, **kwargs)

        return asyncio.create_task(wrapped, name=f"naff:: {event.resolved_name}")

    @staticmethod
    def default_error_handler(source: str, error: BaseException) -> None:
        """
        The default error logging behaviour.

        Args:
            source: The source of this error
            error: The exception itself

        """
        out = traceback.format_exception(error)

        if isinstance(error, HTTPException):
            # HTTPException's are of 3 known formats, we can parse them for human readable errors
            try:
                errors = error.search_for_message(error.errors)
                out = f"HTTPException: {error.status}|{error.response.reason}: " + "\n".join(errors)
            except Exception:  # noqa : S110
                pass

        logger.error(
            "Ignoring exception in {}:{}{}".format(source, "\n" if len(out) > 1 else " ", "".join(out)),
        )

    @Listener.create()
    async def _on_error(self, event: events.Error) -> None:
        await self.on_error(event.source, event.error, *event.args, **event.kwargs)

    async def on_error(self, source: str, error: Exception, *args, **kwargs) -> None:
        """
        Catches all errors dispatched by the library.

        By default it will format and print them to console

        Override this to change error handling behaviour

        """
        self.default_error_handler(source, error)

    async def on_command_error(self, ctx: SendableContext, error: Exception, *args, **kwargs) -> None:
        """
        Catches all errors dispatched by commands.

        By default it will call `Client.on_error`

        Override this to change error handling behavior

        """
        self.dispatch(events.Error(f"cmd /`{ctx.invoke_target}`", error, args, kwargs, ctx))
        try:
            if isinstance(error, errors.CommandOnCooldown):
                await ctx.send(
                    embeds=Embed(
                        description=f"This command is on cooldown!\n"
                        f"Please try again in {int(error.cooldown.get_cooldown_time())} seconds",
                        color=BrandColors.FUCHSIA,
                    )
                )
            elif isinstance(error, errors.MaxConcurrencyReached):
                await ctx.send(
                    embeds=Embed(
                        description="This command has reached its maximum concurrent usage!\n"
                        "Please try again shortly.",
                        color=BrandColors.FUCHSIA,
                    )
                )
            elif isinstance(error, errors.CommandCheckFailure):
                await ctx.send(
                    embeds=Embed(
                        description="You do not have permission to run this command!",
                        color=BrandColors.YELLOW,
                    )
                )
            elif self.send_command_tracebacks:
                out = "".join(traceback.format_exception(error))
                if self.http.token is not None:
                    out = out.replace(self.http.token, "[REDACTED TOKEN]")
                await ctx.send(
                    embeds=Embed(
                        title=f"Error: {type(error).__name__}",
                        color=BrandColors.RED,
                        description=f"```\n{out[:EMBED_MAX_DESC_LENGTH-8]}```",
                    )
                )
        except errors.NaffException:
            pass

    async def on_command(self, ctx: Context) -> None:
        """
        Called *after* any command is ran.

        By default, it will simply log the command, override this to change that behaviour

        Args:
            ctx: The context of the command that was called

        """
        if isinstance(ctx, PrefixedContext):
            symbol = "@"
        elif isinstance(ctx, InteractionContext):
            symbol = "/"
        else:
            symbol = "?"  # likely custom context
        logger.info(f"Command Called: {symbol}{ctx.invoke_target} with {ctx.args = } | {ctx.kwargs = }")

    async def on_component_error(self, ctx: ComponentContext, error: Exception, *args, **kwargs) -> None:
        """
        Catches all errors dispatched by components.

        By default it will call `Naff.on_error`

        Override this to change error handling behavior

        """
        return self.dispatch(events.Error(f"Component Callback for {ctx.custom_id}", error, args, kwargs, ctx))

    async def on_component(self, ctx: ComponentContext) -> None:
        """
        Called *after* any component callback is ran.

        By default, it will simply log the component use, override this to change that behaviour

        Args:
            ctx: The context of the component that was called

        """
        symbol = "¢"
        logger.info(f"Component Called: {symbol}{ctx.invoke_target} with {ctx.args = } | {ctx.kwargs = }")

    async def on_autocomplete_error(self, ctx: AutocompleteContext, error: Exception, *args, **kwargs) -> None:
        """
        Catches all errors dispatched by autocompletion options.

        By default it will call `Naff.on_error`

        Override this to change error handling behavior

        """
        return self.dispatch(
            events.Error(
                f"Autocomplete Callback for /{ctx.invoke_target} - Option: {ctx.focussed_option}",
                error,
                args,
                kwargs,
                ctx,
            )
        )

    async def on_autocomplete(self, ctx: AutocompleteContext) -> None:
        """
        Called *after* any autocomplete callback is ran.

        By default, it will simply log the autocomplete callback, override this to change that behaviour

        Args:
            ctx: The context of the command that was called

        """
        symbol = "$"
        logger.info(f"Autocomplete Called: {symbol}{ctx.invoke_target} with {ctx.args = } | {ctx.kwargs = }")

    @Listener.create()
    async def on_resume(self) -> None:
        self._ready.set()

    @Listener.create()
    async def _on_websocket_ready(self, event: events.RawGatewayEvent) -> None:
        """
        Catches websocket ready and determines when to dispatch the client `READY` signal.

        Args:
            event: The websocket ready packet

        """
        data = event.data
        expected_guilds = {to_snowflake(guild["id"]) for guild in data["guilds"]}
        self._user._add_guilds(expected_guilds)

        if not self._startup:
            while True:
                try:  # wait to let guilds cache
                    await asyncio.wait_for(self._guild_event.wait(), self.guild_event_timeout)
                except asyncio.TimeoutError:
                    logger.warning("Timeout waiting for guilds cache: Not all guilds will be in cache")
                    break
                self._guild_event.clear()

                if len(self.cache.guild_cache) == len(expected_guilds):
                    # all guilds cached
                    break

            if self.fetch_members:
                # ensure all guilds have completed chunking
                for guild in self.guilds:
                    if guild and not guild.chunked.is_set():
                        logger.debug(f"Waiting for {guild.id} to chunk")
                        await guild.chunked.wait()

            # run any pending startup tasks
            if self.async_startup_tasks:
                try:
                    await asyncio.gather(*self.async_startup_tasks)
                except Exception as e:
                    self.dispatch(events.Error("async-extension-loader", e))

            # cache slash commands
            if not self._startup:
                await self._init_interactions()

            self._startup = True
            self.dispatch(events.Startup())

        else:
            # reconnect ready
            ready_guilds = set()

            async def _temp_listener(_event: events.RawGatewayEvent) -> None:
                ready_guilds.add(_event.data["id"])

            listener = Listener.create("_on_raw_guild_create")(_temp_listener)
            self.add_listener(listener)

            while True:
                try:
                    await asyncio.wait_for(self._guild_event.wait(), self.guild_event_timeout)
                    if len(ready_guilds) == len(expected_guilds):
                        break
                except asyncio.TimeoutError:
                    break

            self.listeners["raw_guild_create"].remove(listener)

        self._ready.set()
        self.dispatch(events.Ready())

    async def login(self, token) -> None:
        """
        Login to discord via http.

        !!! note
            You will need to run Naff.start_gateway() before you start receiving gateway events.

        Args:
            token str: Your bot's token

        """
        # i needed somewhere to put this call,
        # login will always run after initialisation
        # so im gathering commands here
        self._gather_commands()

        logger.debug("Attempting to login")
        me = await self.http.login(token.strip())
        self._user = NaffUser.from_dict(me, self)
        self.cache.place_user_data(me)
        self._app = Application.from_dict(await self.http.get_current_bot_information(), self)
        self._mention_reg = re.compile(rf"^(<@!?{self.user.id}*>\s)")

        if self.app.owner:
            self.owner_ids.add(self.app.owner.id)

        self.dispatch(events.Login())

    async def astart(self, token: str) -> None:
        """
        Asynchronous method to start the bot.

        Args:
            token: Your bot's token
        """
        await self.login(token)
        try:
            await self._connection_state.start()
        finally:
            await self.stop()

    def start(self, token: str) -> None:
        """
        Start the bot.

        info:
            This is the recommended method to start the bot
        """
        try:
            asyncio.run(self.astart(token))
        except KeyboardInterrupt:
            # ignore, cus this is useless and can be misleading to the
            # user
            pass

    async def start_gateway(self) -> None:
        """Starts the gateway connection."""
        try:
            await self._connection_state.start()
        finally:
            await self.stop()

    async def stop(self) -> None:
        """Shutdown the bot."""
        logger.debug("Stopping the bot.")
        self._ready.clear()
        await self.http.close()
        await self._connection_state.stop()

    def dispatch(self, event: events.BaseEvent, *args, **kwargs) -> None:
        """
        Dispatch an event.

        Args:
            event: The event to be dispatched.

        """
        listeners = self.listeners.get(event.resolved_name, [])
        if listeners:
            logger.debug(f"Dispatching Event: {event.resolved_name}")
            event.bot = self
            for _listen in listeners:
                try:
                    self._queue_task(_listen, event, *args, **kwargs)
                except Exception as e:
                    raise BotException(
                        f"An error occurred attempting during {event.resolved_name} event processing"
                    ) from e

        _waits = self.waits.get(event.resolved_name, [])
        if _waits:
            index_to_remove = []
            for i, _wait in enumerate(_waits):
                result = _wait(event)
                if result:
                    index_to_remove.append(i)

            for idx in sorted(index_to_remove, reverse=True):
                _waits.pop(idx)

    async def wait_until_ready(self) -> None:
        """Waits for the client to become ready."""
        await self._ready.wait()

    def wait_for(
        self,
        event: Union[str, "BaseEvent"],
        checks: Absent[Optional[Callable[..., bool]]] = MISSING,
        timeout: Optional[float] = None,
    ) -> Any:
        """
        Waits for a WebSocket event to be dispatched.

        Args:
            event: The name of event to wait.
            checks: A predicate to check what to wait for.
            timeout: The number of seconds to wait before timing out.

        Returns:
            The event object.

        """
        event = get_event_name(event)

        if event not in self.waits:
            self.waits[event] = []

        future = asyncio.Future()
        self.waits[event].append(Wait(event, checks, future))

        return asyncio.wait_for(future, timeout)

    async def wait_for_modal(
        self,
        modal: "Modal",
        author: Optional["Snowflake_Type"] = None,
        timeout: Optional[float] = None,
    ) -> ModalContext:
        """
        Wait for a modal response.

        Args:
            modal: The modal we're waiting for.
            author: The user we're waiting for to reply
            timeout: A timeout in seconds to stop waiting

        Returns:
            The context of the modal response

        Raises:
            asyncio.TimeoutError: if no response is received that satisfies the predicate before timeout seconds have passed

        """
        author = to_snowflake(author) if author else None

        def predicate(event) -> bool:
            if modal.custom_id != event.context.custom_id:
                return False
            if author and author != to_snowflake(event.context.author):
                return False
            return True

        resp = await self.wait_for("modal_response", predicate, timeout)
        return resp.context

    async def wait_for_component(
        self,
        messages: Union[Message, int, list] = None,
        components: Optional[
            Union[List[List[Union["BaseComponent", dict]]], List[Union["BaseComponent", dict]], "BaseComponent", dict]
        ] = None,
        check: Optional[Callable] = None,
        timeout: Optional[float] = None,
    ) -> "Component":
        """
        Waits for a component to be sent to the bot.

        Args:
            messages: The message object to check for.
            components: The components to wait for.
            check: A predicate to check what to wait for.
            timeout: The number of seconds to wait before timing out.

        Returns:
            `Component` that was invoked. Use `.context` to get the `ComponentContext`.

        Raises:
            asyncio.TimeoutError: if timed out

        """
        if not (messages or components):
            raise ValueError("You must specify messages or components (or both)")

        message_ids = (
            to_snowflake_list(messages) if isinstance(messages, list) else to_snowflake(messages) if messages else None
        )
        custom_ids = list(get_components_ids(components)) if components else None

        # automatically convert improper custom_ids
        if custom_ids and not all(isinstance(x, str) for x in custom_ids):
            custom_ids = [str(i) for i in custom_ids]

        def _check(event: Component) -> bool:
            ctx: ComponentContext = event.context
            # if custom_ids is empty or there is a match
            wanted_message = not message_ids or ctx.message.id in (
                [message_ids] if isinstance(message_ids, int) else message_ids
            )
            wanted_component = not custom_ids or ctx.custom_id in custom_ids
            if wanted_message and wanted_component:
                if check is None or check(event):
                    return True
                return False
            return False

        return await self.wait_for("component", checks=_check, timeout=timeout)

    def listen(self, event_name: Absent[str] = MISSING) -> Listener:
        """
        A decorator to be used in situations that Naff can't automatically hook your listeners. Ideally, the standard listen decorator should be used, not this.

        Args:
            event_name: The event name to use, if not the coroutine name

        Returns:
            A listener that can be used to hook into the event.

        """

        def wrapper(coro: Callable[..., Coroutine]) -> Listener:
            listener = Listener.create(event_name)(coro)
            self.add_listener(listener)
            return listener

        return wrapper

    def add_event_processor(self, event_name: Absent[str] = MISSING) -> Callable[..., Coroutine]:
        """
        A decorator to be used to add event processors.

        Args:
            event_name: The event name to use, if not the coroutine name

        Returns:
            A function that can be used to hook into the event.

        """

        def wrapper(coro: Callable[..., Coroutine]) -> Callable[..., Coroutine]:
            name = event_name
            if name is MISSING:
                name = coro.__name__
            name = name.lstrip("_")
            name = name.removeprefix("on_")
            self.processors[name] = coro
            return coro

        return wrapper

    def add_listener(self, listener: Listener) -> None:
        """
        Add a listener for an event, if no event is passed, one is determined.

        Args:
            listener Listener: The listener to add to the client

        """
        # check that the required intents are enabled
        event_class_name = "".join([name.capitalize() for name in listener.event.split("_")])
        if event_class := globals().get(event_class_name):
            if required_intents := _INTENT_EVENTS.get(event_class):  # noqa
                if not any(required_intent in self.intents for required_intent in required_intents):
                    self.logger.warning(
                        f"Event `{listener.event}` will not work since the required intent is not set -> Requires any of: `{required_intents}`"
                    )

        if listener.event not in self.listeners:
            self.listeners[listener.event] = []
        self.listeners[listener.event].append(listener)

    def add_interaction(self, command: InteractionCommand) -> bool:
        """
        Add a slash command to the client.

        Args:
            command InteractionCommand: The command to add

        """
        if self.debug_scope:
            command.scopes = [self.debug_scope]

        # for SlashCommand objs without callback (like objects made to hold group info etc)
        if command.callback is None:
            return False

        for scope in command.scopes:
            if scope not in self.interactions:
                self.interactions[scope] = {}
            elif command.resolved_name in self.interactions[scope]:
                old_cmd = self.interactions[scope][command.resolved_name]
                raise ValueError(f"Duplicate Command! {scope}::{old_cmd.resolved_name}")

            if self.enforce_interaction_perms:
                command.checks.append(command._permission_enforcer)  # noqa : w0212

            self.interactions[scope][command.resolved_name] = command

        return True

    def add_hybrid_command(self, command: HybridCommand) -> bool:
        if self.debug_scope:
            command.scopes = [self.debug_scope]

        if command.callback is None:
            return False

        if command.is_subcommand:
            prefixed_base = self.prefixed_commands.get(str(command.name))
            if not prefixed_base:
                prefixed_base = _base_subcommand_generator(
                    str(command.name), list((command.name.to_locale_dict() or {}).values()), str(command.description)
                )
                self.add_prefixed_command(prefixed_base)

            if command.group_name:  # if this is a group command
                _prefixed_cmd = prefixed_base
                prefixed_base = prefixed_base.subcommands.get(str(command.group_name))

                if not prefixed_base:
                    prefixed_base = _base_subcommand_generator(
                        str(command.group_name),
                        list((command.group_name.to_locale_dict() or {}).values()),
                        str(command.group_description),
                        group=True,
                    )
                    _prefixed_cmd.add_command(prefixed_base)

            new_command = _prefixed_from_slash(command)
            new_command._parse_parameters()
            prefixed_base.add_command(new_command)
        else:
            new_command = _prefixed_from_slash(command)
            self.add_prefixed_command(new_command)

        return self.add_interaction(command)

    def add_prefixed_command(self, command: PrefixedCommand) -> None:
        """
        Add a prefixed command to the client.

        Args:
            command PrefixedCommand: The command to add

        """
        # check that the required intent is enabled or the prefix is a mention
        prefixes = (
            self.default_prefix
            if not isinstance(self.default_prefix, str) and not self.default_prefix == MENTION_PREFIX
            else (self.default_prefix,)
        )
        if (MENTION_PREFIX not in prefixes) and (Intents.GUILD_MESSAGE_CONTENT not in self.intents):
            self.logger.warning(
                f"Prefixed commands will not work since the required intent is not set -> Requires: `{Intents.GUILD_MESSAGE_CONTENT.__repr__()}` or usage of the default `MENTION_PREFIX` as the prefix"
            )

        command._parse_parameters()

        if self.prefixed_commands.get(command.name):
            raise ValueError(f"Duplicate command! Multiple commands share the name/alias: {command.name}.")
        self.prefixed_commands[command.name] = command

        for alias in command.aliases:
            if self.prefixed_commands.get(alias):
                raise ValueError(f"Duplicate command! Multiple commands share the name/alias: {alias}.")
            self.prefixed_commands[alias] = command

    def add_component_callback(self, command: ComponentCommand) -> None:
        """
        Add a component callback to the client.

        Args:
            command: The command to add

        """
        for listener in command.listeners:
            # I know this isn't an ideal solution, but it means we can lookup callbacks with O(1)
            if listener not in self._component_callbacks.keys():
                self._component_callbacks[listener] = command
                continue
            else:
                raise ValueError(f"Duplicate Component! Multiple component callbacks for `{listener}`")

    def add_modal_callback(self, command: ModalCommand) -> None:
        """
        Add a modal callback to the client.

        Args:
            command: The command to add
        """
        for listener in command.listeners:
            if listener not in self._modal_callbacks.keys():
                self._modal_callbacks[listener] = command
                continue
            else:
                raise ValueError(f"Duplicate Component! Multiple modal callbacks for `{listener}`")

    def _gather_commands(self) -> None:
        """Gathers commands from __main__ and self."""

        def process(_cmds) -> None:

            for func in _cmds:
                if isinstance(func, ModalCommand):
                    self.add_modal_callback(func)
                elif isinstance(func, ComponentCommand):
                    self.add_component_callback(func)
                elif isinstance(func, HybridCommand):
                    self.add_hybrid_command(func)
                elif isinstance(func, InteractionCommand):
                    self.add_interaction(func)
                elif (
                    isinstance(func, PrefixedCommand) and not func.is_subcommand
                ):  # subcommands will be added with main comamnds
                    self.add_prefixed_command(func)
                elif isinstance(func, Listener):
                    self.add_listener(func)

            logger.debug(f"{len(_cmds)} commands have been loaded from `__main__` and `client`")

        process(
            [obj for _, obj in inspect.getmembers(sys.modules["__main__"]) if isinstance(obj, (BaseCommand, Listener))]
        )
        process(
            [
                obj.copy_with_binding(self)
                for _, obj in inspect.getmembers(self)
                if isinstance(obj, (BaseCommand, Listener))
            ]
        )

        [wrap_partial(obj, self) for _, obj in inspect.getmembers(self) if isinstance(obj, Task)]

    async def _init_interactions(self) -> None:
        """
        Initialise slash commands.

        If `sync_interactions` this will submit all registered slash
        commands to discord. Otherwise, it will get the list of
        interactions and cache their scopes.

        """
        # allow for ext and main to share the same decorator
        try:
            if self.sync_interactions:
                await self.synchronise_interactions()
            else:
                await self._cache_interactions(warn_missing=False)
        except Exception as e:
            self.dispatch(events.Error("Interaction Syncing", e))

    async def _cache_interactions(self, warn_missing: bool = False) -> None:
        """Get all interactions used by this bot and cache them."""
        if warn_missing or self.del_unused_app_cmd:
            bot_scopes = {g.id for g in self.cache.guild_cache.values()}
            bot_scopes.add(GLOBAL_SCOPE)
        else:
            bot_scopes = set(self.interactions)

        req_lock = asyncio.Lock()

        async def wrap(*args, **kwargs) -> Absent[List[Dict]]:
            async with req_lock:
                # throttle this
                await asyncio.sleep(0.1)
            try:
                return await self.http.get_application_commands(*args, **kwargs)
            except Forbidden:
                return MISSING

        results = await asyncio.gather(*[wrap(self.app.id, scope) for scope in bot_scopes])
        results = dict(zip(bot_scopes, results))

        for scope, remote_cmds in results.items():
            if remote_cmds == MISSING:
                logger.debug(f"Bot was not invited to guild {scope} with `application.commands` scope")
                continue

            remote_cmds = {cmd_data["name"]: cmd_data for cmd_data in remote_cmds}
            found = set()  # this is a temporary hack to fix subcommand detection
            if scope in self.interactions:
                for cmd in self.interactions[scope].values():
                    cmd_name = str(cmd.name)
                    cmd_data = remote_cmds.get(cmd_name, MISSING)
                    if cmd_data is MISSING:
                        if cmd_name not in found:
                            if warn_missing:
                                logger.error(
                                    f'Detected yet to sync slash command "/{cmd_name}" for scope '
                                    f"{'global' if scope == GLOBAL_SCOPE else scope}"
                                )
                        continue
                    else:
                        found.add(cmd_name)
                    self._interaction_scopes[str(cmd_data["id"])] = scope
                    cmd.cmd_id[scope] = int(cmd_data["id"])

            if warn_missing:
                for cmd_data in remote_cmds.values():
                    logger.error(
                        f"Detected unimplemented slash command \"/{cmd_data['name']}\" for scope "
                        f"{'global' if scope == GLOBAL_SCOPE else scope}"
                    )

    async def synchronise_interactions(
        self, *, scopes: Sequence["Snowflake_Type"] = MISSING, delete_commands: Absent[bool] = MISSING
    ) -> None:
        """
        Synchronise registered interactions with discord.

        Args:
            scopes: Optionally specify which scopes are to be synced
            delete_commands: Override the client setting and delete commands
        """
        s = time.perf_counter()
        _delete_cmds = self.del_unused_app_cmd if delete_commands is MISSING else delete_commands
        await self._cache_interactions()

        if scopes is not MISSING:
            cmd_scopes = scopes
        elif self.del_unused_app_cmd:
            # if we're deleting unused commands, we check all scopes
            cmd_scopes = [to_snowflake(g_id) for g_id in self._user._guild_ids] + [GLOBAL_SCOPE]
        else:
            # if we're not deleting, just check the scopes we have cmds registered in
            cmd_scopes = list(set(self.interactions) | {GLOBAL_SCOPE})

        local_cmds_json = application_commands_to_dict(self.interactions)

        async def sync_scope(cmd_scope) -> None:

            sync_needed_flag = False  # a flag to force this scope to synchronise
            sync_payload = []  # the payload to be pushed to discord

            try:
                try:
                    remote_commands = await self.http.get_application_commands(self.app.id, cmd_scope)
                except Forbidden:
                    logger.warning(f"Bot is lacking `application.commands` scope in {cmd_scope}!")
                    return

                for local_cmd in self.interactions.get(cmd_scope, {}).values():
                    # get remote equivalent of this command
                    remote_cmd_json = next(
                        (v for v in remote_commands if int(v["id"]) == local_cmd.cmd_id.get(cmd_scope)), None
                    )
                    # get json representation of this command
                    local_cmd_json = next((c for c in local_cmds_json[cmd_scope] if c["name"] == str(local_cmd.name)))

                    # this works by adding any command we *want* on Discord, to a payload, and synchronising that
                    # this allows us to delete unused commands, add new commands, or do nothing in 1 or less API calls

                    if sync_needed(local_cmd_json, remote_cmd_json):
                        # determine if the local and remote commands are out-of-sync
                        sync_needed_flag = True
                        sync_payload.append(local_cmd_json)
                    elif not _delete_cmds and remote_cmd_json:
                        _remote_payload = {
                            k: v for k, v in remote_cmd_json.items() if k not in ("id", "application_id", "version")
                        }
                        sync_payload.append(_remote_payload)
                    elif _delete_cmds:
                        sync_payload.append(local_cmd_json)

                sync_payload = [json.loads(_dump) for _dump in {json.dumps(_cmd) for _cmd in sync_payload}]

                if sync_needed_flag or (_delete_cmds and len(sync_payload) < len(remote_commands)):
                    # synchronise commands if flag is set, or commands are to be deleted
                    logger.info(f"Overwriting {cmd_scope} with {len(sync_payload)} application commands")
                    sync_response: list[dict] = await self.http.overwrite_application_commands(
                        self.app.id, sync_payload, cmd_scope
                    )
                    self._cache_sync_response(sync_response, cmd_scope)
                else:
                    logger.debug(f"{cmd_scope} is already up-to-date with {len(remote_commands)} commands.")

            except Forbidden as e:
                raise InteractionMissingAccess(cmd_scope) from e
            except HTTPException as e:
                self._raise_sync_exception(e, local_cmds_json, cmd_scope)

        await asyncio.gather(*[sync_scope(scope) for scope in cmd_scopes])

        t = time.perf_counter() - s
        logger.debug(f"Sync of {len(cmd_scopes)} scopes took {t} seconds")

    def get_application_cmd_by_id(self, cmd_id: "Snowflake_Type") -> Optional[InteractionCommand]:
        """
        Get a application command from the internal cache by its ID.

        Args:
            cmd_id: The ID of the command

        Returns:
            The command, if one with the given ID exists internally, otherwise None

        """
        scope = self._interaction_scopes.get(str(cmd_id), MISSING)
        cmd_id = int(cmd_id)  # ensure int ID
        if scope != MISSING:
            for cmd in self.interactions[scope].values():
                if int(cmd.cmd_id.get(scope)) == cmd_id:
                    return cmd
        return None

    @staticmethod
    def _raise_sync_exception(e: HTTPException, cmds_json: dict, cmd_scope: "Snowflake_Type") -> NoReturn:
        try:
            if isinstance(e.errors, dict):
                for cmd_num in e.errors.keys():
                    cmd = cmds_json[cmd_scope][int(cmd_num)]
                    output = e.search_for_message(e.errors[cmd_num], cmd)
                    if len(output) > 1:
                        output = "\n".join(output)
                        logger.error(f"Multiple Errors found in command `{cmd['name']}`:\n{output}")
                    else:
                        logger.error(f"Error in command `{cmd['name']}`: {output[0]}")
            else:
                raise e from None
        except Exception:
            # the above shouldn't fail, but if it does, just raise the exception normally
            raise e from None

    def _cache_sync_response(self, sync_response: list[dict], scope: "Snowflake_Type") -> None:
        for cmd_data in sync_response:
            self._interaction_scopes[cmd_data["id"]] = scope
            if cmd_data["name"] in self.interactions[scope]:
                self.interactions[scope][cmd_data["name"]].cmd_id[scope] = int(cmd_data["id"])
            else:
                # sub_cmd
                for sc in cmd_data["options"]:
                    if sc["type"] == OptionTypes.SUB_COMMAND:
                        if f"{cmd_data['name']} {sc['name']}" in self.interactions[scope]:
                            self.interactions[scope][f"{cmd_data['name']} {sc['name']}"].cmd_id[scope] = int(
                                cmd_data["id"]
                            )
                    elif sc["type"] == OptionTypes.SUB_COMMAND_GROUP:
                        for _sc in sc["options"]:
                            if f"{cmd_data['name']} {sc['name']} {_sc['name']}" in self.interactions[scope]:
                                self.interactions[scope][f"{cmd_data['name']} {sc['name']} {_sc['name']}"].cmd_id[
                                    scope
                                ] = int(cmd_data["id"])

    @overload
    async def get_context(self, data: ComponentChannelInteractionData, interaction: Literal[True]) -> ComponentContext:
        ...

    @overload
    async def get_context(
        self, data: AutocompleteChannelInteractionData, interaction: Literal[True]
    ) -> AutocompleteContext:
        ...

    # as of right now, discord_typings doesn't include anything like this
    # @overload
    # async def get_context(self, data: ModalSubmitInteractionData, interaction: Literal[True]) -> ModalContext:
    #     ...

    @overload
    async def get_context(self, data: InteractionData, interaction: Literal[True]) -> InteractionContext:
        ...

    @overload
    async def get_context(
        self, data: dict, interaction: Literal[True]
    ) -> ComponentContext | AutocompleteContext | ModalContext | InteractionContext:
        # fallback case since some data isn't typehinted properly
        ...

    @overload
    async def get_context(self, data: Message, interaction: Literal[False] = False) -> PrefixedContext:
        ...

    async def get_context(
        self, data: InteractionData | dict | Message, interaction: bool = False
    ) -> ComponentContext | AutocompleteContext | ModalContext | InteractionContext | PrefixedContext:
        """
        Return a context object based on data passed.

        !!! note
            If you want to use custom context objects, this is the method to override. Your replacement must take the same arguments as this, and return a Context-like object.

        Args:
            data: The data of the event
            interaction: Is this an interaction or not?

        Returns:
            Context object

        """
        # this line shuts up IDE warnings
        cls: ComponentContext | AutocompleteContext | ModalContext | InteractionContext | PrefixedContext

        if interaction:
            match data["type"]:
                case InteractionTypes.MESSAGE_COMPONENT:
                    cls = self.component_context.from_dict(data, self)

                case InteractionTypes.AUTOCOMPLETE:
                    cls = self.autocomplete_context.from_dict(data, self)

                case InteractionTypes.MODAL_RESPONSE:
                    cls = self.modal_context.from_dict(data, self)

                case _:
                    cls = self.interaction_context.from_dict(data, self)

            if not cls.channel:
                try:
                    cls.channel = await self.cache.fetch_channel(data["channel_id"])
                except Forbidden:
                    cls.channel = BaseChannel.from_dict_factory(
                        {"id": data["channel_id"], "type": ChannelTypes.GUILD_TEXT}, self
                    )

        else:
            cls = self.prefixed_context.from_message(self, data)
            if not cls.channel:
                cls.channel = await self.cache.fetch_channel(data._channel_id)

        return cls

    async def _run_slash_command(self, command: SlashCommand, ctx: InteractionContext) -> Any:
        """Overrideable method that executes slash commands, can be used to wrap callback execution"""
        return await command(ctx, **ctx.kwargs)

    async def _run_prefixed_command(self, command: PrefixedCommand, ctx: PrefixedContext) -> Any:
        """Overrideable method that executes prefixed commands, can be used to wrap callback execution"""
        return await command(ctx)

    @processors.Processor.define("raw_interaction_create")
    async def _dispatch_interaction(self, event: RawGatewayEvent) -> None:
        """
        Identify and dispatch interaction of slash commands or components.

        Args:
            raw interaction event

        """
        interaction_data = event.data

        if interaction_data["type"] in (
            InteractionTypes.PING,
            InteractionTypes.APPLICATION_COMMAND,
            InteractionTypes.AUTOCOMPLETE,
        ):
            interaction_id = interaction_data["data"]["id"]
            name = interaction_data["data"]["name"]
            scope = self._interaction_scopes.get(str(interaction_id))

            if scope in self.interactions:
                ctx = await self.get_context(interaction_data, True)

                ctx.command: SlashCommand = self.interactions[scope][ctx.invoke_target]  # type: ignore
                logger.debug(f"{scope} :: {ctx.command.name} should be called")

                if ctx.command.auto_defer:
                    auto_defer = ctx.command.auto_defer
                elif ctx.command.extension and ctx.command.extension.auto_defer:
                    auto_defer = ctx.command.extension.auto_defer
                else:
                    auto_defer = self.auto_defer

                if auto_opt := getattr(ctx, "focussed_option", None):
                    try:
                        await ctx.command.autocomplete_callbacks[auto_opt](ctx, **ctx.kwargs)
                    except Exception as e:
                        await self.on_autocomplete_error(ctx, e)
                    finally:
                        await self.on_autocomplete(ctx)
                else:
                    try:
                        await auto_defer(ctx)
                        if self.pre_run_callback:
                            await self.pre_run_callback(ctx, **ctx.kwargs)
                        await self._run_slash_command(ctx.command, ctx)
                        if self.post_run_callback:
                            await self.post_run_callback(ctx, **ctx.kwargs)
                    except Exception as e:
                        await self.on_command_error(ctx, e)
                    finally:
                        await self.on_command(ctx)
            else:
                logger.error(f"Unknown cmd_id received:: {interaction_id} ({name})")

        elif interaction_data["type"] == InteractionTypes.MESSAGE_COMPONENT:
            # Buttons, Selects, ContextMenu::Message
            ctx = await self.get_context(interaction_data, True)
            component_type = interaction_data["data"]["component_type"]

            self.dispatch(events.Component(ctx))
            if callback := self._component_callbacks.get(ctx.custom_id):
                ctx.command = callback
                try:
                    if self.pre_run_callback:
                        await self.pre_run_callback(ctx)
                    await callback(ctx)
                    if self.post_run_callback:
                        await self.post_run_callback(ctx)
                except Exception as e:
                    await self.on_component_error(ctx, e)
                finally:
                    await self.on_component(ctx)
            if component_type == ComponentTypes.BUTTON:
                self.dispatch(events.Button(ctx))
            if component_type == ComponentTypes.SELECT:
                self.dispatch(events.Select(ctx))

        elif interaction_data["type"] == InteractionTypes.MODAL_RESPONSE:
            ctx = await self.get_context(interaction_data, True)
            self.dispatch(events.ModalResponse(ctx))

            # todo: Polls remove this icky code duplication - love from past-polls ❤️
            if callback := self._modal_callbacks.get(ctx.custom_id):
                ctx.command = callback

                try:
                    if self.pre_run_callback:
                        await self.pre_run_callback(ctx)
                    await callback(ctx)
                    if self.post_run_callback:
                        await self.post_run_callback(ctx)
                except Exception as e:
                    await self.on_component_error(ctx, e)
                finally:
                    await self.on_component(ctx)

        else:
            raise NotImplementedError(f"Unknown Interaction Received: {interaction_data['type']}")

    @Listener.create("message_create")
    async def _dispatch_prefixed_commands(self, event: MessageCreate) -> None:
        """Determine if a prefixed command is being triggered, and dispatch it."""
        message = event.message

        if not message.content:
            return

        if not message.author.bot:
            prefixes: str | Iterable[str] = await self.generate_prefixes(self, message)

            if isinstance(prefixes, str) or prefixes == MENTION_PREFIX:
                # its easier to treat everything as if it may be an iterable
                # rather than building a special case for this
                prefixes = (prefixes,)  # type: ignore

            prefix_used = None

            for prefix in prefixes:
                if prefix == MENTION_PREFIX:
                    if mention := self._mention_reg.search(message.content):  # type: ignore
                        prefix = mention.group()
                    else:
                        continue

                if message.content.startswith(prefix):
                    prefix_used = prefix
                    break

            if prefix_used:
                context = await self.get_context(message)
                context.prefix = prefix_used

                # interestingly enough, we cannot count on ctx.invoke_target
                # being correct as its hard to account for newlines and the like
                # with the way we get subcommands here
                # we'll have to reconstruct it by getting the content_parameters
                # then removing the prefix and the parameters from the message
                # content
                content_parameters = message.content.removeprefix(prefix_used)  # type: ignore
                command = self  # yes, this is a hack

                while True:
                    first_word: str = get_first_word(content_parameters)  # type: ignore
                    if isinstance(command, PrefixedCommand):
                        new_command = command.subcommands.get(first_word)
                    else:
                        new_command = command.prefixed_commands.get(first_word)
                    if not new_command or not new_command.enabled:
                        break

                    command = new_command
                    content_parameters = content_parameters.removeprefix(first_word).strip()

                    if command.subcommands and command.hierarchical_checking:
                        try:
                            await new_command._can_run(context)  # will error out if we can't run this command
                        except Exception as e:
                            if new_command.error_callback:
                                await new_command.error_callback(e, context)
                            elif new_command.extension and new_command.extension.extension_error:
                                await new_command.extension.extension_error(context)
                            else:
                                await self.on_command_error(context, e)
                            return

                if not isinstance(command, PrefixedCommand):
                    command = None

                if command and command.enabled:
                    # yeah, this looks ugly
                    context.command = command
                    context.invoke_target = (
                        message.content.removeprefix(prefix_used).removesuffix(content_parameters).strip()  # type: ignore
                    )
                    context.args = get_args(context.content_parameters)
                    try:
                        if self.pre_run_callback:
                            await self.pre_run_callback(context)
                        await self._run_prefixed_command(command, context)
                        if self.post_run_callback:
                            await self.post_run_callback(context)
                    except Exception as e:
                        await self.on_command_error(context, e)
                    finally:
                        await self.on_command(context)

    @Listener.create("disconnect")
    async def _disconnect(self) -> None:
        self._ready.clear()

    def get_extensions(self, name: str) -> list[Extension]:
        """
        Get all ext with a name or extension name.

        Args:
            name: The name of the extension, or the name of it's extension

        Returns:
            List of Extensions
        """
        if name not in self.ext.keys():
            return [ext for ext in self.ext.values() if ext.extension_name == name]

        return [self.ext.get(name, None)]

    def get_ext(self, name: str) -> Extension | None:
        """
        Get a extension with a name or extension name.

        Args:
            name: The name of the extension, or the name of it's extension

        Returns:
            A extension, if found
        """
        if ext := self.get_extensions(name):
            return ext[0]
        return None

    def load_extension(self, name: str, package: str | None = None, **load_kwargs: Mapping[str, Any]) -> None:
        """
        Load an extension with given arguments.

        Args:
            name: The name of the extension.
            package: The package the extension is in
            **load_kwargs: The auto-filled mapping of the load keyword arguments

        """
        name = importlib.util.resolve_name(name, package)
        if name in self.__modules:
            raise Exception(f"{name} already loaded")

        module = importlib.import_module(name, package)
        try:
            setup = getattr(module, "setup", None)
            if not setup:
                raise ExtensionLoadException(
                    f"{name} lacks an entry point. Ensure you have a function called `setup` defined in that file"
                ) from None
            setup(self, **load_kwargs)
        except ExtensionLoadException:
            raise
        except Exception as e:
            del sys.modules[name]
            raise ExtensionLoadException(f"Unexpected Error loading {name}") from e

        else:
            logger.debug(f"Loaded Extension: {name}")
            self.__modules[name] = module

            if self.sync_ext and self._ready.is_set():
                try:
                    asyncio.get_running_loop()
                except RuntimeError:
                    return
                asyncio.create_task(self.synchronise_interactions())

    def unload_extension(self, name: str, package: str | None = None, **unload_kwargs: Mapping[str, Any]) -> None:
        """
        Unload an extension with given arguments.

        Args:
            name: The name of the extension.
            package: The package the extension is in
            **unload_kwargs: The auto-filled mapping of the unload keyword arguments

        """
        name = importlib.util.resolve_name(name, package)
        module = self.__modules.get(name)

        if module is None:
            raise ExtensionNotFound(f"No extension called {name} is loaded")

        try:
            teardown = getattr(module, "teardown")
            teardown(**unload_kwargs)
        except AttributeError:
            pass

        for ext in self.get_extensions(name):
            ext.drop(**unload_kwargs)

        del sys.modules[name]
        del self.__modules[name]

        if self.sync_ext and self._ready.is_set():
            if self.sync_ext and self._ready.is_set():
                try:
                    asyncio.get_running_loop()
                except RuntimeError:
                    return
                asyncio.create_task(self.synchronise_interactions())

    def reload_extension(
        self,
        name: str,
        package: str | None = None,
        *,
        load_kwargs: Mapping[str, Any] = None,
        unload_kwargs: Mapping[str, Any] = None,
    ) -> None:
        """
        Helper method to reload an extension. Simply unloads, then loads the extension with given arguments.

        Args:
            name: The name of the extension.
            package: The package the extension is in
            load_kwargs: The manually-filled mapping of the load keyword arguments
            unload_kwargs: The manually-filled mapping of the unload keyword arguments

        """
        name = importlib.util.resolve_name(name, package)
        module = self.__modules.get(name)

        if module is None:
            logger.warning("Attempted to reload extension thats not loaded. Loading extension instead")
            return self.load_extension(name, package)

        if not load_kwargs:
            load_kwargs = {}
        if not unload_kwargs:
            unload_kwargs = {}

        self.unload_extension(name, package, **unload_kwargs)
        self.load_extension(name, package, **load_kwargs)

        # todo: maybe add an ability to revert to the previous version if unable to load the new one

    async def fetch_guild(self, guild_id: "Snowflake_Type") -> Optional[Guild]:
        """
        Fetch a guild.

        !!! note
            This method is an alias for the cache which will either return a cached object, or query discord for the object
            if its not already cached.

        Args:
            guild_id: The ID of the guild to get

        Returns:
            Guild Object if found, otherwise None

        """
        try:
            return await self.cache.fetch_guild(guild_id)
        except NotFound:
            return None

    def get_guild(self, guild_id: "Snowflake_Type") -> Optional[Guild]:
        """
        Get a guild.

        !!! note
            This method is an alias for the cache which will return a cached object.

        Args:
            guild_id: The ID of the guild to get

        Returns:
            Guild Object if found, otherwise None

        """
        return self.cache.get_guild(guild_id)

    async def create_guild_from_template(
        self,
        template_code: Union["GuildTemplate", str],
        name: str,
        icon: Absent[UPLOADABLE_TYPE] = MISSING,
    ) -> Optional[Guild]:
        """
        Creates a new guild based on a template.

        !!! note
            This endpoint can only be used by bots in less than 10 guilds.

        Args:
            template_code: The code of the template to use.
            name: The name of the guild (2-100 characters)
            icon: Location or File of icon to set

        Returns:
            The newly created guild object

        """
        if isinstance(template_code, GuildTemplate):
            template_code = template_code.code

        if icon:
            icon = to_image_data(icon)
        guild_data = await self.http.create_guild_from_guild_template(template_code, name, icon)
        return Guild.from_dict(guild_data, self)

    async def fetch_channel(self, channel_id: "Snowflake_Type") -> Optional["TYPE_ALL_CHANNEL"]:
        """
        Fetch a channel.

        !!! note
            This method is an alias for the cache which will either return a cached object, or query discord for the object
            if its not already cached.

        Args:
            channel_id: The ID of the channel to get

        Returns:
            Channel Object if found, otherwise None

        """
        try:
            return await self.cache.fetch_channel(channel_id)
        except NotFound:
            return None

    def get_channel(self, channel_id: "Snowflake_Type") -> Optional["TYPE_ALL_CHANNEL"]:
        """
        Get a channel.

        !!! note
            This method is an alias for the cache which will return a cached object.

        Args:
            channel_id: The ID of the channel to get

        Returns:
            Channel Object if found, otherwise None

        """
        return self.cache.get_channel(channel_id)

    async def fetch_user(self, user_id: "Snowflake_Type") -> Optional[User]:
        """
        Fetch a user.

        !!! note
            This method is an alias for the cache which will either return a cached object, or query discord for the object
            if its not already cached.

        Args:
            user_id: The ID of the user to get

        Returns:
            User Object if found, otherwise None

        """
        try:
            return await self.cache.fetch_user(user_id)
        except NotFound:
            return None

    def get_user(self, user_id: "Snowflake_Type") -> Optional[User]:
        """
        Get a user.

        !!! note
            This method is an alias for the cache which will return a cached object.

        Args:
            user_id: The ID of the user to get

        Returns:
            User Object if found, otherwise None

        """
        return self.cache.get_user(user_id)

    async def fetch_member(self, user_id: "Snowflake_Type", guild_id: "Snowflake_Type") -> Optional[Member]:
        """
        Fetch a member from a guild.

        !!! note
            This method is an alias for the cache which will either return a cached object, or query discord for the object
            if its not already cached.

        Args:
            user_id: The ID of the member
            guild_id: The ID of the guild to get the member from

        Returns:
            Member object if found, otherwise None

        """
        try:
            return await self.cache.fetch_member(guild_id, user_id)
        except NotFound:
            return None

    def get_member(self, user_id: "Snowflake_Type", guild_id: "Snowflake_Type") -> Optional[Member]:
        """
        Get a member from a guild.

        !!! note
            This method is an alias for the cache which will return a cached object.

        Args:
            user_id: The ID of the member
            guild_id: The ID of the guild to get the member from

        Returns:
            Member object if found, otherwise None

        """
        return self.cache.get_member(guild_id, user_id)

    async def fetch_scheduled_event(
        self, guild_id: "Snowflake_Type", scheduled_event_id: "Snowflake_Type", with_user_count: bool = False
    ) -> Optional["ScheduledEvent"]:
        """
        Fetch a scheduled event by id.

        Args:
            guild_id: The ID of the guild to get the scheduled event from
            scheduled_event_id: The ID of the scheduled event to get
            with_user_count: Whether to include the user count in the response

        Returns:
            The scheduled event if found, otherwise None

        """
        try:
            scheduled_event_data = await self.http.get_scheduled_event(guild_id, scheduled_event_id, with_user_count)
            return ScheduledEvent.from_dict(scheduled_event_data, self)
        except NotFound:
            return None

    async def fetch_custom_emoji(self, emoji_id: "Snowflake_Type", guild_id: "Snowflake_Type") -> Optional[CustomEmoji]:
        """
        Fetch a custom emoji by id.

        Args:
            emoji_id: The id of the custom emoji.
            guild_id: The id of the guild the emoji belongs to.

        Returns:
            The custom emoji if found, otherwise None.

        """
        try:
            return await self.cache.fetch_emoji(guild_id, emoji_id)
        except NotFound:
            return None

    def get_custom_emoji(
        self, emoji_id: "Snowflake_Type", guild_id: Optional["Snowflake_Type"] = None
    ) -> Optional[CustomEmoji]:
        """
        Get a custom emoji by id.

        Args:
            emoji_id: The id of the custom emoji.
            guild_id: The id of the guild the emoji belongs to.

        Returns:
            The custom emoji if found, otherwise None.

        """
        emoji = self.cache.get_emoji(emoji_id)
        if emoji and (not guild_id or emoji._guild_id == to_snowflake(guild_id)):
            return emoji
        return None

    async def fetch_sticker(self, sticker_id: "Snowflake_Type") -> Optional[Sticker]:
        """
        Fetch a sticker by ID.

        Args:
            sticker_id: The ID of the sticker.

        Returns:
            A sticker object if found, otherwise None

        """
        try:
            sticker_data = await self.http.get_sticker(sticker_id)
            return Sticker.from_dict(sticker_data, self)
        except NotFound:
            return None

    async def fetch_nitro_packs(self) -> Optional[List["StickerPack"]]:
        """
        List the sticker packs available to Nitro subscribers.

        Returns:
            A list of StickerPack objects if found, otherwise returns None

        """
        try:
            packs_data = await self.http.list_nitro_sticker_packs()
            return [StickerPack.from_dict(data, self) for data in packs_data]

        except NotFound:
            return None

    async def fetch_voice_regions(self) -> List["VoiceRegion"]:
        """
        List the voice regions available on Discord.

        Returns:
            A list of voice regions.

        """
        regions_data = await self.http.list_voice_regions()
        regions = VoiceRegion.from_list(regions_data)
        return regions

    async def connect_to_vc(
        self, guild_id: "Snowflake_Type", channel_id: "Snowflake_Type", muted: bool = False, deafened: bool = False
    ) -> ActiveVoiceState:
        """
        Connect the bot to a voice channel.

        Args:
            guild_id: id of the guild the voice channel is in.
            channel_id: id of the voice channel client wants to join.
            muted: Whether the bot should be muted when connected.
            deafened: Whether the bot should be deafened when connected.

        Returns:
            The new active voice state on successfully connection.

        """
        return await self._connection_state.voice_connect(guild_id, channel_id, muted, deafened)

    def get_bot_voice_state(self, guild_id: "Snowflake_Type") -> Optional[ActiveVoiceState]:
        """
        Get the bot's voice state for a guild.

        Args:
            guild_id: The target guild's id.

        Returns:
            The bot's voice state for the guild if connected, otherwise None.

        """
        return self._connection_state.get_voice_state(guild_id)

    async def change_presence(
        self, status: Optional[Union[str, Status]] = Status.ONLINE, activity: Optional[Union[Activity, str]] = None
    ) -> None:
        """
        Change the bots presence.

        Args:
            status: The status for the bot to be. i.e. online, afk, etc.
            activity: The activity for the bot to be displayed as doing.

        !!! note
            Bots may only be `playing` `streaming` `listening` `watching` or `competing`, other activity types are likely to fail.

        """
        await self._connection_state.change_presence(status, activity)

logger = logger instance-attribute

The logger NAFF should use. Do not use in combination with Client.basic_logging and Client.logging_level.

Note

Different loggers with multiple clients are not supported

sync_interactions: bool = sync_interactions instance-attribute

Should application commands be synced

del_unused_app_cmd: bool = delete_unused_application_cmds instance-attribute

Should unused application commands be deleted?

sync_ext: bool = sync_ext instance-attribute

Should we sync whenever a extension is (un)loaded

debug_scope = to_snowflake(debug_scope) if debug_scope is not MISSING else MISSING instance-attribute

Sync global commands as guild for quicker command updates during debug

default_prefix = default_prefix instance-attribute

The default prefix to be used for prefixed commands

send_command_tracebacks: bool = send_command_tracebacks instance-attribute

Should the traceback of command errors be sent in reply to the command invocation

auto_defer = auto_defer instance-attribute

A system to automatically defer commands after a set duration

http: HTTPClient = HTTPClient() instance-attribute

The HTTP client to use when interacting with discord endpoints

interaction_context: Type[InteractionContext] = interaction_context instance-attribute

The object to instantiate for Interaction Context

prefixed_context: Type[PrefixedContext] = prefixed_context instance-attribute

The object to instantiate for Prefixed Context

component_context: Type[ComponentContext] = component_context instance-attribute

The object to instantiate for Component Context

autocomplete_context: Type[AutocompleteContext] = autocomplete_context instance-attribute

The object to instantiate for Autocomplete Context

modal_context: Type[ModalContext] = modal_context instance-attribute

The object to instantiate for Modal Context

hybrid_context: Type[HybridContext] = hybrid_context instance-attribute

The object to instantiate for Hybrid Context

guild_event_timeout = 3 instance-attribute

How long to wait for guilds to be cached

fetch_members = fetch_members instance-attribute

Fetch the full members list of all guilds on startup

prefixed_commands: Dict[str, PrefixedCommand] = {} instance-attribute

A dictionary of registered prefixed commands: {name: command}

interactions: Dict[Snowflake_Type, Dict[str, InteractionCommand]] = {} instance-attribute

A dictionary of registered application commands: {cmd_id: command}

ext = {} instance-attribute

A dictionary of mounted ext

async_startup_tasks: list[Coroutine] = [] instance-attribute

A list of coroutines to run during startup

is_closed() property

Returns True if the bot has closed.

Source code in naff/client/client.py
407
408
409
410
@property
def is_closed(self) -> bool:
    """Returns True if the bot has closed."""
    return self._closed

is_ready() property

Returns True if the bot is ready.

Source code in naff/client/client.py
412
413
414
415
@property
def is_ready(self) -> bool:
    """Returns True if the bot is ready."""
    return self._ready.is_set()

latency() property

Returns the latency of the websocket connection.

Source code in naff/client/client.py
417
418
419
420
@property
def latency(self) -> float:
    """Returns the latency of the websocket connection."""
    return self._connection_state.latency

average_latency() property

Returns the average latency of the websocket connection.

Source code in naff/client/client.py
422
423
424
425
@property
def average_latency(self) -> float:
    """Returns the average latency of the websocket connection."""
    return self._connection_state.average_latency

start_time() property

The start time of the bot.

Source code in naff/client/client.py
427
428
429
430
@property
def start_time(self) -> datetime:
    """The start time of the bot."""
    return self._connection_state.start_time

gateway_started() property

Returns if the gateway has been started.

Source code in naff/client/client.py
432
433
434
435
@property
def gateway_started(self) -> bool:
    """Returns if the gateway has been started."""
    return self._connection_state.gateway_started.is_set()

user() property

Returns the bot's user.

Source code in naff/client/client.py
437
438
439
440
@property
def user(self) -> NaffUser:
    """Returns the bot's user."""
    return self._user

app() property

Returns the bots application.

Source code in naff/client/client.py
442
443
444
445
@property
def app(self) -> Application:
    """Returns the bots application."""
    return self._app

owner() property

Returns the bot's owner'.

Source code in naff/client/client.py
447
448
449
450
451
452
453
@property
def owner(self) -> Optional["User"]:
    """Returns the bot's owner'."""
    try:
        return self.app.owner
    except TypeError:
        return MISSING

owners() property

Returns the bot's owners as declared via client.owner_ids.

Source code in naff/client/client.py
455
456
457
458
@property
def owners(self) -> List["User"]:
    """Returns the bot's owners as declared via `client.owner_ids`."""
    return [self.get_user(u_id) for u_id in self.owner_ids]

guilds() property

Returns a list of all guilds the bot is in.

Source code in naff/client/client.py
460
461
462
463
@property
def guilds(self) -> List["Guild"]:
    """Returns a list of all guilds the bot is in."""
    return self.user.guilds

status() property

Get the status of the bot.

IE online, afk, dnd

Source code in naff/client/client.py
465
466
467
468
469
470
471
472
473
@property
def status(self) -> Status:
    """
    Get the status of the bot.

    IE online, afk, dnd

    """
    return self._status

activity() property

Get the activity of the bot.

Source code in naff/client/client.py
475
476
477
478
@property
def activity(self) -> Activity:
    """Get the activity of the bot."""
    return self._activity

application_commands() property

A list of all application commands registered within the bot.

Source code in naff/client/client.py
480
481
482
483
484
485
486
487
@property
def application_commands(self) -> List[InteractionCommand]:
    """A list of all application commands registered within the bot."""
    commands = []
    for scope in self.interactions.keys():
        commands += [cmd for cmd in self.interactions[scope].values() if cmd not in commands]

    return commands

ws() property

Returns the websocket client.

Source code in naff/client/client.py
489
490
491
492
@property
def ws(self) -> GatewayClient:
    """Returns the websocket client."""
    return self._connection_state.gateway

generate_prefixes(bot, message) async

A method to get the bot's default_prefix, can be overridden to add dynamic prefixes.

Note

To easily override this method, simply use the generate_prefixes parameter when instantiating the client

Parameters:

Name Type Description Default
bot Client

A reference to the client

required
message Message

A message to determine the prefix from.

required
Example
1
2
3
4
5
6
async def generate_prefixes(bot, message):
    if message.guild.id == 870046872864165888:
        return ["!"]
    return bot.default_prefix

bot = Client(generate_prefixes=generate_prefixes, ...)

Returns:

Type Description
str | Iterable[str]

A string or an iterable of strings to use as a prefix. By default, this will return client.default_prefix

Source code in naff/client/client.py
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
async def generate_prefixes(self, bot: "Client", message: Message) -> str | Iterable[str]:
    """
    A method to get the bot's default_prefix, can be overridden to add dynamic prefixes.

    !!! note
        To easily override this method, simply use the `generate_prefixes` parameter when instantiating the client

    Args:
        bot: A reference to the client
        message: A message to determine the prefix from.

    Example:
        ```python
        async def generate_prefixes(bot, message):
            if message.guild.id == 870046872864165888:
                return ["!"]
            return bot.default_prefix

        bot = Client(generate_prefixes=generate_prefixes, ...)
        ```

    Returns:
        A string or an iterable of strings to use as a prefix. By default, this will return `client.default_prefix`

    """
    return self.default_prefix

default_error_handler(source, error) staticmethod

The default error logging behaviour.

Parameters:

Name Type Description Default
source str

The source of this error

required
error BaseException

The exception itself

required
Source code in naff/client/client.py
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
@staticmethod
def default_error_handler(source: str, error: BaseException) -> None:
    """
    The default error logging behaviour.

    Args:
        source: The source of this error
        error: The exception itself

    """
    out = traceback.format_exception(error)

    if isinstance(error, HTTPException):
        # HTTPException's are of 3 known formats, we can parse them for human readable errors
        try:
            errors = error.search_for_message(error.errors)
            out = f"HTTPException: {error.status}|{error.response.reason}: " + "\n".join(errors)
        except Exception:  # noqa : S110
            pass

    logger.error(
        "Ignoring exception in {}:{}{}".format(source, "\n" if len(out) > 1 else " ", "".join(out)),
    )

on_error(source, error, *args, **kwargs) async

Catches all errors dispatched by the library.

By default it will format and print them to console

Override this to change error handling behaviour

Source code in naff/client/client.py
604
605
606
607
608
609
610
611
612
613
async def on_error(self, source: str, error: Exception, *args, **kwargs) -> None:
    """
    Catches all errors dispatched by the library.

    By default it will format and print them to console

    Override this to change error handling behaviour

    """
    self.default_error_handler(source, error)

on_command_error(ctx, error, *args, **kwargs) async

Catches all errors dispatched by commands.

By default it will call Client.on_error

Override this to change error handling behavior

Source code in naff/client/client.py
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
async def on_command_error(self, ctx: SendableContext, error: Exception, *args, **kwargs) -> None:
    """
    Catches all errors dispatched by commands.

    By default it will call `Client.on_error`

    Override this to change error handling behavior

    """
    self.dispatch(events.Error(f"cmd /`{ctx.invoke_target}`", error, args, kwargs, ctx))
    try:
        if isinstance(error, errors.CommandOnCooldown):
            await ctx.send(
                embeds=Embed(
                    description=f"This command is on cooldown!\n"
                    f"Please try again in {int(error.cooldown.get_cooldown_time())} seconds",
                    color=BrandColors.FUCHSIA,
                )
            )
        elif isinstance(error, errors.MaxConcurrencyReached):
            await ctx.send(
                embeds=Embed(
                    description="This command has reached its maximum concurrent usage!\n"
                    "Please try again shortly.",
                    color=BrandColors.FUCHSIA,
                )
            )
        elif isinstance(error, errors.CommandCheckFailure):
            await ctx.send(
                embeds=Embed(
                    description="You do not have permission to run this command!",
                    color=BrandColors.YELLOW,
                )
            )
        elif self.send_command_tracebacks:
            out = "".join(traceback.format_exception(error))
            if self.http.token is not None:
                out = out.replace(self.http.token, "[REDACTED TOKEN]")
            await ctx.send(
                embeds=Embed(
                    title=f"Error: {type(error).__name__}",
                    color=BrandColors.RED,
                    description=f"```\n{out[:EMBED_MAX_DESC_LENGTH-8]}```",
                )
            )
    except errors.NaffException:
        pass

on_command(ctx) async

Called after any command is ran.

By default, it will simply log the command, override this to change that behaviour

Parameters:

Name Type Description Default
ctx Context

The context of the command that was called

required
Source code in naff/client/client.py
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
async def on_command(self, ctx: Context) -> None:
    """
    Called *after* any command is ran.

    By default, it will simply log the command, override this to change that behaviour

    Args:
        ctx: The context of the command that was called

    """
    if isinstance(ctx, PrefixedContext):
        symbol = "@"
    elif isinstance(ctx, InteractionContext):
        symbol = "/"
    else:
        symbol = "?"  # likely custom context
    logger.info(f"Command Called: {symbol}{ctx.invoke_target} with {ctx.args = } | {ctx.kwargs = }")

on_component_error(ctx, error, *args, **kwargs) async

Catches all errors dispatched by components.

By default it will call Naff.on_error

Override this to change error handling behavior

Source code in naff/client/client.py
681
682
683
684
685
686
687
688
689
690
async def on_component_error(self, ctx: ComponentContext, error: Exception, *args, **kwargs) -> None:
    """
    Catches all errors dispatched by components.

    By default it will call `Naff.on_error`

    Override this to change error handling behavior

    """
    return self.dispatch(events.Error(f"Component Callback for {ctx.custom_id}", error, args, kwargs, ctx))

on_component(ctx) async

Called after any component callback is ran.

By default, it will simply log the component use, override this to change that behaviour

Parameters:

Name Type Description Default
ctx ComponentContext

The context of the component that was called

required
Source code in naff/client/client.py
692
693
694
695
696
697
698
699
700
701
702
703
async def on_component(self, ctx: ComponentContext) -> None:
    """
    Called *after* any component callback is ran.

    By default, it will simply log the component use, override this to change that behaviour

    Args:
        ctx: The context of the component that was called

    """
    symbol = "¢"
    logger.info(f"Component Called: {symbol}{ctx.invoke_target} with {ctx.args = } | {ctx.kwargs = }")

on_autocomplete_error(ctx, error, *args, **kwargs) async

Catches all errors dispatched by autocompletion options.

By default it will call Naff.on_error

Override this to change error handling behavior

Source code in naff/client/client.py
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
async def on_autocomplete_error(self, ctx: AutocompleteContext, error: Exception, *args, **kwargs) -> None:
    """
    Catches all errors dispatched by autocompletion options.

    By default it will call `Naff.on_error`

    Override this to change error handling behavior

    """
    return self.dispatch(
        events.Error(
            f"Autocomplete Callback for /{ctx.invoke_target} - Option: {ctx.focussed_option}",
            error,
            args,
            kwargs,
            ctx,
        )
    )

on_autocomplete(ctx) async

Called after any autocomplete callback is ran.

By default, it will simply log the autocomplete callback, override this to change that behaviour

Parameters:

Name Type Description Default
ctx AutocompleteContext

The context of the command that was called

required
Source code in naff/client/client.py
724
725
726
727
728
729
730
731
732
733
734
735
async def on_autocomplete(self, ctx: AutocompleteContext) -> None:
    """
    Called *after* any autocomplete callback is ran.

    By default, it will simply log the autocomplete callback, override this to change that behaviour

    Args:
        ctx: The context of the command that was called

    """
    symbol = "$"
    logger.info(f"Autocomplete Called: {symbol}{ctx.invoke_target} with {ctx.args = } | {ctx.kwargs = }")

login(token) async

Login to discord via http.

Note

You will need to run Naff.start_gateway() before you start receiving gateway events.

Parameters:

Name Type Description Default
token str

Your bot's token

required
Source code in naff/client/client.py
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
async def login(self, token) -> None:
    """
    Login to discord via http.

    !!! note
        You will need to run Naff.start_gateway() before you start receiving gateway events.

    Args:
        token str: Your bot's token

    """
    # i needed somewhere to put this call,
    # login will always run after initialisation
    # so im gathering commands here
    self._gather_commands()

    logger.debug("Attempting to login")
    me = await self.http.login(token.strip())
    self._user = NaffUser.from_dict(me, self)
    self.cache.place_user_data(me)
    self._app = Application.from_dict(await self.http.get_current_bot_information(), self)
    self._mention_reg = re.compile(rf"^(<@!?{self.user.id}*>\s)")

    if self.app.owner:
        self.owner_ids.add(self.app.owner.id)

    self.dispatch(events.Login())

astart(token) async

Asynchronous method to start the bot.

Parameters:

Name Type Description Default
token str

Your bot's token

required
Source code in naff/client/client.py
839
840
841
842
843
844
845
846
847
848
849
850
async def astart(self, token: str) -> None:
    """
    Asynchronous method to start the bot.

    Args:
        token: Your bot's token
    """
    await self.login(token)
    try:
        await self._connection_state.start()
    finally:
        await self.stop()

start(token)

Start the bot.

info

This is the recommended method to start the bot

Source code in naff/client/client.py
852
853
854
855
856
857
858
859
860
861
862
863
864
def start(self, token: str) -> None:
    """
    Start the bot.

    info:
        This is the recommended method to start the bot
    """
    try:
        asyncio.run(self.astart(token))
    except KeyboardInterrupt:
        # ignore, cus this is useless and can be misleading to the
        # user
        pass

start_gateway() async

Starts the gateway connection.

Source code in naff/client/client.py
866
867
868
869
870
871
async def start_gateway(self) -> None:
    """Starts the gateway connection."""
    try:
        await self._connection_state.start()
    finally:
        await self.stop()

stop() async

Shutdown the bot.

Source code in naff/client/client.py
873
874
875
876
877
878
async def stop(self) -> None:
    """Shutdown the bot."""
    logger.debug("Stopping the bot.")
    self._ready.clear()
    await self.http.close()
    await self._connection_state.stop()

dispatch(event, *args, **kwargs)

Dispatch an event.

Parameters:

Name Type Description Default
event events.BaseEvent

The event to be dispatched.

required
Source code in naff/client/client.py
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
def dispatch(self, event: events.BaseEvent, *args, **kwargs) -> None:
    """
    Dispatch an event.

    Args:
        event: The event to be dispatched.

    """
    listeners = self.listeners.get(event.resolved_name, [])
    if listeners:
        logger.debug(f"Dispatching Event: {event.resolved_name}")
        event.bot = self
        for _listen in listeners:
            try:
                self._queue_task(_listen, event, *args, **kwargs)
            except Exception as e:
                raise BotException(
                    f"An error occurred attempting during {event.resolved_name} event processing"
                ) from e

    _waits = self.waits.get(event.resolved_name, [])
    if _waits:
        index_to_remove = []
        for i, _wait in enumerate(_waits):
            result = _wait(event)
            if result:
                index_to_remove.append(i)

        for idx in sorted(index_to_remove, reverse=True):
            _waits.pop(idx)

wait_until_ready() async

Waits for the client to become ready.

Source code in naff/client/client.py
911
912
913
async def wait_until_ready(self) -> None:
    """Waits for the client to become ready."""
    await self._ready.wait()

wait_for(event, checks=MISSING, timeout=None)

Waits for a WebSocket event to be dispatched.

Parameters:

Name Type Description Default
event Union[str, BaseEvent]

The name of event to wait.

required
checks Absent[Optional[Callable[..., bool]]]

A predicate to check what to wait for.

MISSING
timeout Optional[float]

The number of seconds to wait before timing out.

None

Returns:

Type Description
Any

The event object.

Source code in naff/client/client.py
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
def wait_for(
    self,
    event: Union[str, "BaseEvent"],
    checks: Absent[Optional[Callable[..., bool]]] = MISSING,
    timeout: Optional[float] = None,
) -> Any:
    """
    Waits for a WebSocket event to be dispatched.

    Args:
        event: The name of event to wait.
        checks: A predicate to check what to wait for.
        timeout: The number of seconds to wait before timing out.

    Returns:
        The event object.

    """
    event = get_event_name(event)

    if event not in self.waits:
        self.waits[event] = []

    future = asyncio.Future()
    self.waits[event].append(Wait(event, checks, future))

    return asyncio.wait_for(future, timeout)

wait_for_modal(modal, author=None, timeout=None) async

Wait for a modal response.

Parameters:

Name Type Description Default
modal Modal

The modal we're waiting for.

required
author Optional[Snowflake_Type]

The user we're waiting for to reply

None
timeout Optional[float]

A timeout in seconds to stop waiting

None

Returns:

Type Description
ModalContext

The context of the modal response

Raises:

Type Description
asyncio.TimeoutError

if no response is received that satisfies the predicate before timeout seconds have passed

Source code in naff/client/client.py
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
async def wait_for_modal(
    self,
    modal: "Modal",
    author: Optional["Snowflake_Type"] = None,
    timeout: Optional[float] = None,
) -> ModalContext:
    """
    Wait for a modal response.

    Args:
        modal: The modal we're waiting for.
        author: The user we're waiting for to reply
        timeout: A timeout in seconds to stop waiting

    Returns:
        The context of the modal response

    Raises:
        asyncio.TimeoutError: if no response is received that satisfies the predicate before timeout seconds have passed

    """
    author = to_snowflake(author) if author else None

    def predicate(event) -> bool:
        if modal.custom_id != event.context.custom_id:
            return False
        if author and author != to_snowflake(event.context.author):
            return False
        return True

    resp = await self.wait_for("modal_response", predicate, timeout)
    return resp.context

wait_for_component(messages=None, components=None, check=None, timeout=None) async

Waits for a component to be sent to the bot.

Parameters:

Name Type Description Default
messages Union[Message, int, list]

The message object to check for.

None
components Optional[Union[List[List[Union[BaseComponent, dict]]], List[Union[BaseComponent, dict]], BaseComponent, dict]]

The components to wait for.

None
check Optional[Callable]

A predicate to check what to wait for.

None
timeout Optional[float]

The number of seconds to wait before timing out.

None

Returns:

Type Description
Component

Component that was invoked. Use .context to get the ComponentContext.

Raises:

Type Description
asyncio.TimeoutError

if timed out

Source code in naff/client/client.py
 976
 977
 978
 979
 980
 981
 982
 983
 984
 985
 986
 987
 988
 989
 990
 991
 992
 993
 994
 995
 996
 997
 998
 999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
async def wait_for_component(
    self,
    messages: Union[Message, int, list] = None,
    components: Optional[
        Union[List[List[Union["BaseComponent", dict]]], List[Union["BaseComponent", dict]], "BaseComponent", dict]
    ] = None,
    check: Optional[Callable] = None,
    timeout: Optional[float] = None,
) -> "Component":
    """
    Waits for a component to be sent to the bot.

    Args:
        messages: The message object to check for.
        components: The components to wait for.
        check: A predicate to check what to wait for.
        timeout: The number of seconds to wait before timing out.

    Returns:
        `Component` that was invoked. Use `.context` to get the `ComponentContext`.

    Raises:
        asyncio.TimeoutError: if timed out

    """
    if not (messages or components):
        raise ValueError("You must specify messages or components (or both)")

    message_ids = (
        to_snowflake_list(messages) if isinstance(messages, list) else to_snowflake(messages) if messages else None
    )
    custom_ids = list(get_components_ids(components)) if components else None

    # automatically convert improper custom_ids
    if custom_ids and not all(isinstance(x, str) for x in custom_ids):
        custom_ids = [str(i) for i in custom_ids]

    def _check(event: Component) -> bool:
        ctx: ComponentContext = event.context
        # if custom_ids is empty or there is a match
        wanted_message = not message_ids or ctx.message.id in (
            [message_ids] if isinstance(message_ids, int) else message_ids
        )
        wanted_component = not custom_ids or ctx.custom_id in custom_ids
        if wanted_message and wanted_component:
            if check is None or check(event):
                return True
            return False
        return False

    return await self.wait_for("component", checks=_check, timeout=timeout)

listen(event_name=MISSING)

A decorator to be used in situations that Naff can't automatically hook your listeners. Ideally, the standard listen decorator should be used, not this.

Parameters:

Name Type Description Default
event_name Absent[str]

The event name to use, if not the coroutine name

MISSING

Returns:

Type Description
Listener

A listener that can be used to hook into the event.

Source code in naff/client/client.py
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
def listen(self, event_name: Absent[str] = MISSING) -> Listener:
    """
    A decorator to be used in situations that Naff can't automatically hook your listeners. Ideally, the standard listen decorator should be used, not this.

    Args:
        event_name: The event name to use, if not the coroutine name

    Returns:
        A listener that can be used to hook into the event.

    """

    def wrapper(coro: Callable[..., Coroutine]) -> Listener:
        listener = Listener.create(event_name)(coro)
        self.add_listener(listener)
        return listener

    return wrapper

add_event_processor(event_name=MISSING)

A decorator to be used to add event processors.

Parameters:

Name Type Description Default
event_name Absent[str]

The event name to use, if not the coroutine name

MISSING

Returns:

Type Description
Callable[..., Coroutine]

A function that can be used to hook into the event.

Source code in naff/client/client.py
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
def add_event_processor(self, event_name: Absent[str] = MISSING) -> Callable[..., Coroutine]:
    """
    A decorator to be used to add event processors.

    Args:
        event_name: The event name to use, if not the coroutine name

    Returns:
        A function that can be used to hook into the event.

    """

    def wrapper(coro: Callable[..., Coroutine]) -> Callable[..., Coroutine]:
        name = event_name
        if name is MISSING:
            name = coro.__name__
        name = name.lstrip("_")
        name = name.removeprefix("on_")
        self.processors[name] = coro
        return coro

    return wrapper

add_listener(listener)

Add a listener for an event, if no event is passed, one is determined.

Parameters:

Name Type Description Default
listener Listener

The listener to add to the client

required
Source code in naff/client/client.py
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
def add_listener(self, listener: Listener) -> None:
    """
    Add a listener for an event, if no event is passed, one is determined.

    Args:
        listener Listener: The listener to add to the client

    """
    # check that the required intents are enabled
    event_class_name = "".join([name.capitalize() for name in listener.event.split("_")])
    if event_class := globals().get(event_class_name):
        if required_intents := _INTENT_EVENTS.get(event_class):  # noqa
            if not any(required_intent in self.intents for required_intent in required_intents):
                self.logger.warning(
                    f"Event `{listener.event}` will not work since the required intent is not set -> Requires any of: `{required_intents}`"
                )

    if listener.event not in self.listeners:
        self.listeners[listener.event] = []
    self.listeners[listener.event].append(listener)

add_interaction(command)

Add a slash command to the client.

Parameters:

Name Type Description Default
command InteractionCommand

The command to add

required
Source code in naff/client/client.py
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
def add_interaction(self, command: InteractionCommand) -> bool:
    """
    Add a slash command to the client.

    Args:
        command InteractionCommand: The command to add

    """
    if self.debug_scope:
        command.scopes = [self.debug_scope]

    # for SlashCommand objs without callback (like objects made to hold group info etc)
    if command.callback is None:
        return False

    for scope in command.scopes:
        if scope not in self.interactions:
            self.interactions[scope] = {}
        elif command.resolved_name in self.interactions[scope]:
            old_cmd = self.interactions[scope][command.resolved_name]
            raise ValueError(f"Duplicate Command! {scope}::{old_cmd.resolved_name}")

        if self.enforce_interaction_perms:
            command.checks.append(command._permission_enforcer)  # noqa : w0212

        self.interactions[scope][command.resolved_name] = command

    return True

add_prefixed_command(command)

Add a prefixed command to the client.

Parameters:

Name Type Description Default
command PrefixedCommand

The command to add

required
Source code in naff/client/client.py
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
def add_prefixed_command(self, command: PrefixedCommand) -> None:
    """
    Add a prefixed command to the client.

    Args:
        command PrefixedCommand: The command to add

    """
    # check that the required intent is enabled or the prefix is a mention
    prefixes = (
        self.default_prefix
        if not isinstance(self.default_prefix, str) and not self.default_prefix == MENTION_PREFIX
        else (self.default_prefix,)
    )
    if (MENTION_PREFIX not in prefixes) and (Intents.GUILD_MESSAGE_CONTENT not in self.intents):
        self.logger.warning(
            f"Prefixed commands will not work since the required intent is not set -> Requires: `{Intents.GUILD_MESSAGE_CONTENT.__repr__()}` or usage of the default `MENTION_PREFIX` as the prefix"
        )

    command._parse_parameters()

    if self.prefixed_commands.get(command.name):
        raise ValueError(f"Duplicate command! Multiple commands share the name/alias: {command.name}.")
    self.prefixed_commands[command.name] = command

    for alias in command.aliases:
        if self.prefixed_commands.get(alias):
            raise ValueError(f"Duplicate command! Multiple commands share the name/alias: {alias}.")
        self.prefixed_commands[alias] = command

add_component_callback(command)

Add a component callback to the client.

Parameters:

Name Type Description Default
command ComponentCommand

The command to add

required
Source code in naff/client/client.py
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
def add_component_callback(self, command: ComponentCommand) -> None:
    """
    Add a component callback to the client.

    Args:
        command: The command to add

    """
    for listener in command.listeners:
        # I know this isn't an ideal solution, but it means we can lookup callbacks with O(1)
        if listener not in self._component_callbacks.keys():
            self._component_callbacks[listener] = command
            continue
        else:
            raise ValueError(f"Duplicate Component! Multiple component callbacks for `{listener}`")

add_modal_callback(command)

Add a modal callback to the client.

Parameters:

Name Type Description Default
command ModalCommand

The command to add

required
Source code in naff/client/client.py
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
def add_modal_callback(self, command: ModalCommand) -> None:
    """
    Add a modal callback to the client.

    Args:
        command: The command to add
    """
    for listener in command.listeners:
        if listener not in self._modal_callbacks.keys():
            self._modal_callbacks[listener] = command
            continue
        else:
            raise ValueError(f"Duplicate Component! Multiple modal callbacks for `{listener}`")

synchronise_interactions(*, scopes=MISSING, delete_commands=MISSING) async

Synchronise registered interactions with discord.

Parameters:

Name Type Description Default
scopes Sequence[Snowflake_Type]

Optionally specify which scopes are to be synced

MISSING
delete_commands Absent[bool]

Override the client setting and delete commands

MISSING
Source code in naff/client/client.py
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
async def synchronise_interactions(
    self, *, scopes: Sequence["Snowflake_Type"] = MISSING, delete_commands: Absent[bool] = MISSING
) -> None:
    """
    Synchronise registered interactions with discord.

    Args:
        scopes: Optionally specify which scopes are to be synced
        delete_commands: Override the client setting and delete commands
    """
    s = time.perf_counter()
    _delete_cmds = self.del_unused_app_cmd if delete_commands is MISSING else delete_commands
    await self._cache_interactions()

    if scopes is not MISSING:
        cmd_scopes = scopes
    elif self.del_unused_app_cmd:
        # if we're deleting unused commands, we check all scopes
        cmd_scopes = [to_snowflake(g_id) for g_id in self._user._guild_ids] + [GLOBAL_SCOPE]
    else:
        # if we're not deleting, just check the scopes we have cmds registered in
        cmd_scopes = list(set(self.interactions) | {GLOBAL_SCOPE})

    local_cmds_json = application_commands_to_dict(self.interactions)

    async def sync_scope(cmd_scope) -> None:

        sync_needed_flag = False  # a flag to force this scope to synchronise
        sync_payload = []  # the payload to be pushed to discord

        try:
            try:
                remote_commands = await self.http.get_application_commands(self.app.id, cmd_scope)
            except Forbidden:
                logger.warning(f"Bot is lacking `application.commands` scope in {cmd_scope}!")
                return

            for local_cmd in self.interactions.get(cmd_scope, {}).values():
                # get remote equivalent of this command
                remote_cmd_json = next(
                    (v for v in remote_commands if int(v["id"]) == local_cmd.cmd_id.get(cmd_scope)), None
                )
                # get json representation of this command
                local_cmd_json = next((c for c in local_cmds_json[cmd_scope] if c["name"] == str(local_cmd.name)))

                # this works by adding any command we *want* on Discord, to a payload, and synchronising that
                # this allows us to delete unused commands, add new commands, or do nothing in 1 or less API calls

                if sync_needed(local_cmd_json, remote_cmd_json):
                    # determine if the local and remote commands are out-of-sync
                    sync_needed_flag = True
                    sync_payload.append(local_cmd_json)
                elif not _delete_cmds and remote_cmd_json:
                    _remote_payload = {
                        k: v for k, v in remote_cmd_json.items() if k not in ("id", "application_id", "version")
                    }
                    sync_payload.append(_remote_payload)
                elif _delete_cmds:
                    sync_payload.append(local_cmd_json)

            sync_payload = [json.loads(_dump) for _dump in {json.dumps(_cmd) for _cmd in sync_payload}]

            if sync_needed_flag or (_delete_cmds and len(sync_payload) < len(remote_commands)):
                # synchronise commands if flag is set, or commands are to be deleted
                logger.info(f"Overwriting {cmd_scope} with {len(sync_payload)} application commands")
                sync_response: list[dict] = await self.http.overwrite_application_commands(
                    self.app.id, sync_payload, cmd_scope
                )
                self._cache_sync_response(sync_response, cmd_scope)
            else:
                logger.debug(f"{cmd_scope} is already up-to-date with {len(remote_commands)} commands.")

        except Forbidden as e:
            raise InteractionMissingAccess(cmd_scope) from e
        except HTTPException as e:
            self._raise_sync_exception(e, local_cmds_json, cmd_scope)

    await asyncio.gather(*[sync_scope(scope) for scope in cmd_scopes])

    t = time.perf_counter() - s
    logger.debug(f"Sync of {len(cmd_scopes)} scopes took {t} seconds")

get_application_cmd_by_id(cmd_id)

Get a application command from the internal cache by its ID.

Parameters:

Name Type Description Default
cmd_id Snowflake_Type

The ID of the command

required

Returns:

Type Description
Optional[InteractionCommand]

The command, if one with the given ID exists internally, otherwise None

Source code in naff/client/client.py
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
def get_application_cmd_by_id(self, cmd_id: "Snowflake_Type") -> Optional[InteractionCommand]:
    """
    Get a application command from the internal cache by its ID.

    Args:
        cmd_id: The ID of the command

    Returns:
        The command, if one with the given ID exists internally, otherwise None

    """
    scope = self._interaction_scopes.get(str(cmd_id), MISSING)
    cmd_id = int(cmd_id)  # ensure int ID
    if scope != MISSING:
        for cmd in self.interactions[scope].values():
            if int(cmd.cmd_id.get(scope)) == cmd_id:
                return cmd
    return None

get_context(data, interaction=False) async

Return a context object based on data passed.

Note

If you want to use custom context objects, this is the method to override. Your replacement must take the same arguments as this, and return a Context-like object.

Parameters:

Name Type Description Default
data InteractionData | dict | Message

The data of the event

required
interaction bool

Is this an interaction or not?

False

Returns:

Type Description
ComponentContext | AutocompleteContext | ModalContext | InteractionContext | PrefixedContext

Context object

Source code in naff/client/client.py
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
async def get_context(
    self, data: InteractionData | dict | Message, interaction: bool = False
) -> ComponentContext | AutocompleteContext | ModalContext | InteractionContext | PrefixedContext:
    """
    Return a context object based on data passed.

    !!! note
        If you want to use custom context objects, this is the method to override. Your replacement must take the same arguments as this, and return a Context-like object.

    Args:
        data: The data of the event
        interaction: Is this an interaction or not?

    Returns:
        Context object

    """
    # this line shuts up IDE warnings
    cls: ComponentContext | AutocompleteContext | ModalContext | InteractionContext | PrefixedContext

    if interaction:
        match data["type"]:
            case InteractionTypes.MESSAGE_COMPONENT:
                cls = self.component_context.from_dict(data, self)

            case InteractionTypes.AUTOCOMPLETE:
                cls = self.autocomplete_context.from_dict(data, self)

            case InteractionTypes.MODAL_RESPONSE:
                cls = self.modal_context.from_dict(data, self)

            case _:
                cls = self.interaction_context.from_dict(data, self)

        if not cls.channel:
            try:
                cls.channel = await self.cache.fetch_channel(data["channel_id"])
            except Forbidden:
                cls.channel = BaseChannel.from_dict_factory(
                    {"id": data["channel_id"], "type": ChannelTypes.GUILD_TEXT}, self
                )

    else:
        cls = self.prefixed_context.from_message(self, data)
        if not cls.channel:
            cls.channel = await self.cache.fetch_channel(data._channel_id)

    return cls

get_extensions(name)

Get all ext with a name or extension name.

Parameters:

Name Type Description Default
name str

The name of the extension, or the name of it's extension

required

Returns:

Type Description
list[Extension]

List of Extensions

Source code in naff/client/client.py
1741
1742
1743
1744
1745
1746
1747
1748
1749
1750
1751
1752
1753
1754
def get_extensions(self, name: str) -> list[Extension]:
    """
    Get all ext with a name or extension name.

    Args:
        name: The name of the extension, or the name of it's extension

    Returns:
        List of Extensions
    """
    if name not in self.ext.keys():
        return [ext for ext in self.ext.values() if ext.extension_name == name]

    return [self.ext.get(name, None)]

get_ext(name)

Get a extension with a name or extension name.

Parameters:

Name Type Description Default
name str

The name of the extension, or the name of it's extension

required

Returns:

Type Description
Extension | None

A extension, if found

Source code in naff/client/client.py
1756
1757
1758
1759
1760
1761
1762
1763
1764
1765
1766
1767
1768
def get_ext(self, name: str) -> Extension | None:
    """
    Get a extension with a name or extension name.

    Args:
        name: The name of the extension, or the name of it's extension

    Returns:
        A extension, if found
    """
    if ext := self.get_extensions(name):
        return ext[0]
    return None

load_extension(name, package=None, **load_kwargs)

Load an extension with given arguments.

Parameters:

Name Type Description Default
name str

The name of the extension.

required
package str | None

The package the extension is in

None
**load_kwargs Mapping[str, Any]

The auto-filled mapping of the load keyword arguments

{}
Source code in naff/client/client.py
1770
1771
1772
1773
1774
1775
1776
1777
1778
1779
1780
1781
1782
1783
1784
1785
1786
1787
1788
1789
1790
1791
1792
1793
1794
1795
1796
1797
1798
1799
1800
1801
1802
1803
1804
1805
1806
1807
def load_extension(self, name: str, package: str | None = None, **load_kwargs: Mapping[str, Any]) -> None:
    """
    Load an extension with given arguments.

    Args:
        name: The name of the extension.
        package: The package the extension is in
        **load_kwargs: The auto-filled mapping of the load keyword arguments

    """
    name = importlib.util.resolve_name(name, package)
    if name in self.__modules:
        raise Exception(f"{name} already loaded")

    module = importlib.import_module(name, package)
    try:
        setup = getattr(module, "setup", None)
        if not setup:
            raise ExtensionLoadException(
                f"{name} lacks an entry point. Ensure you have a function called `setup` defined in that file"
            ) from None
        setup(self, **load_kwargs)
    except ExtensionLoadException:
        raise
    except Exception as e:
        del sys.modules[name]
        raise ExtensionLoadException(f"Unexpected Error loading {name}") from e

    else:
        logger.debug(f"Loaded Extension: {name}")
        self.__modules[name] = module

        if self.sync_ext and self._ready.is_set():
            try:
                asyncio.get_running_loop()
            except RuntimeError:
                return
            asyncio.create_task(self.synchronise_interactions())

unload_extension(name, package=None, **unload_kwargs)

Unload an extension with given arguments.

Parameters:

Name Type Description Default
name str

The name of the extension.

required
package str | None

The package the extension is in

None
**unload_kwargs Mapping[str, Any]

The auto-filled mapping of the unload keyword arguments

{}
Source code in naff/client/client.py
1809
1810
1811
1812
1813
1814
1815
1816
1817
1818
1819
1820
1821
1822
1823
1824
1825
1826
1827
1828
1829
1830
1831
1832
1833
1834
1835
1836
1837
1838
1839
1840
1841
1842
1843
def unload_extension(self, name: str, package: str | None = None, **unload_kwargs: Mapping[str, Any]) -> None:
    """
    Unload an extension with given arguments.

    Args:
        name: The name of the extension.
        package: The package the extension is in
        **unload_kwargs: The auto-filled mapping of the unload keyword arguments

    """
    name = importlib.util.resolve_name(name, package)
    module = self.__modules.get(name)

    if module is None:
        raise ExtensionNotFound(f"No extension called {name} is loaded")

    try:
        teardown = getattr(module, "teardown")
        teardown(**unload_kwargs)
    except AttributeError:
        pass

    for ext in self.get_extensions(name):
        ext.drop(**unload_kwargs)

    del sys.modules[name]
    del self.__modules[name]

    if self.sync_ext and self._ready.is_set():
        if self.sync_ext and self._ready.is_set():
            try:
                asyncio.get_running_loop()
            except RuntimeError:
                return
            asyncio.create_task(self.synchronise_interactions())

reload_extension(name, package=None, *, load_kwargs=None, unload_kwargs=None)

Helper method to reload an extension. Simply unloads, then loads the extension with given arguments.

Parameters:

Name Type Description Default
name str

The name of the extension.

required
package str | None

The package the extension is in

None
load_kwargs Mapping[str, Any]

The manually-filled mapping of the load keyword arguments

None
unload_kwargs Mapping[str, Any]

The manually-filled mapping of the unload keyword arguments

None
Source code in naff/client/client.py
1845
1846
1847
1848
1849
1850
1851
1852
1853
1854
1855
1856
1857
1858
1859
1860
1861
1862
1863
1864
1865
1866
1867
1868
1869
1870
1871
1872
1873
1874
1875
1876
def reload_extension(
    self,
    name: str,
    package: str | None = None,
    *,
    load_kwargs: Mapping[str, Any] = None,
    unload_kwargs: Mapping[str, Any] = None,
) -> None:
    """
    Helper method to reload an extension. Simply unloads, then loads the extension with given arguments.

    Args:
        name: The name of the extension.
        package: The package the extension is in
        load_kwargs: The manually-filled mapping of the load keyword arguments
        unload_kwargs: The manually-filled mapping of the unload keyword arguments

    """
    name = importlib.util.resolve_name(name, package)
    module = self.__modules.get(name)

    if module is None:
        logger.warning("Attempted to reload extension thats not loaded. Loading extension instead")
        return self.load_extension(name, package)

    if not load_kwargs:
        load_kwargs = {}
    if not unload_kwargs:
        unload_kwargs = {}

    self.unload_extension(name, package, **unload_kwargs)
    self.load_extension(name, package, **load_kwargs)

fetch_guild(guild_id) async

Fetch a guild.

Note

This method is an alias for the cache which will either return a cached object, or query discord for the object if its not already cached.

Parameters:

Name Type Description Default
guild_id Snowflake_Type

The ID of the guild to get

required

Returns:

Type Description
Optional[Guild]

Guild Object if found, otherwise None

Source code in naff/client/client.py
1880
1881
1882
1883
1884
1885
1886
1887
1888
1889
1890
1891
1892
1893
1894
1895
1896
1897
1898
async def fetch_guild(self, guild_id: "Snowflake_Type") -> Optional[Guild]:
    """
    Fetch a guild.

    !!! note
        This method is an alias for the cache which will either return a cached object, or query discord for the object
        if its not already cached.

    Args:
        guild_id: The ID of the guild to get

    Returns:
        Guild Object if found, otherwise None

    """
    try:
        return await self.cache.fetch_guild(guild_id)
    except NotFound:
        return None

get_guild(guild_id)

Get a guild.

Note

This method is an alias for the cache which will return a cached object.

Parameters:

Name Type Description Default
guild_id Snowflake_Type

The ID of the guild to get

required

Returns:

Type Description
Optional[Guild]

Guild Object if found, otherwise None

Source code in naff/client/client.py
1900
1901
1902
1903
1904
1905
1906
1907
1908
1909
1910
1911
1912
1913
1914
def get_guild(self, guild_id: "Snowflake_Type") -> Optional[Guild]:
    """
    Get a guild.

    !!! note
        This method is an alias for the cache which will return a cached object.

    Args:
        guild_id: The ID of the guild to get

    Returns:
        Guild Object if found, otherwise None

    """
    return self.cache.get_guild(guild_id)

create_guild_from_template(template_code, name, icon=MISSING) async

Creates a new guild based on a template.

Note

This endpoint can only be used by bots in less than 10 guilds.

Parameters:

Name Type Description Default
template_code Union[GuildTemplate, str]

The code of the template to use.

required
name str

The name of the guild (2-100 characters)

required
icon Absent[UPLOADABLE_TYPE]

Location or File of icon to set

MISSING

Returns:

Type Description
Optional[Guild]

The newly created guild object

Source code in naff/client/client.py
1916
1917
1918
1919
1920
1921
1922
1923
1924
1925
1926
1927
1928
1929
1930
1931
1932
1933
1934
1935
1936
1937
1938
1939
1940
1941
1942
1943
async def create_guild_from_template(
    self,
    template_code: Union["GuildTemplate", str],
    name: str,
    icon: Absent[UPLOADABLE_TYPE] = MISSING,
) -> Optional[Guild]:
    """
    Creates a new guild based on a template.

    !!! note
        This endpoint can only be used by bots in less than 10 guilds.

    Args:
        template_code: The code of the template to use.
        name: The name of the guild (2-100 characters)
        icon: Location or File of icon to set

    Returns:
        The newly created guild object

    """
    if isinstance(template_code, GuildTemplate):
        template_code = template_code.code

    if icon:
        icon = to_image_data(icon)
    guild_data = await self.http.create_guild_from_guild_template(template_code, name, icon)
    return Guild.from_dict(guild_data, self)

fetch_channel(channel_id) async

Fetch a channel.

Note

This method is an alias for the cache which will either return a cached object, or query discord for the object if its not already cached.

Parameters:

Name Type Description Default
channel_id Snowflake_Type

The ID of the channel to get

required

Returns:

Type Description
Optional[TYPE_ALL_CHANNEL]

Channel Object if found, otherwise None

Source code in naff/client/client.py
1945
1946
1947
1948
1949
1950
1951
1952
1953
1954
1955
1956
1957
1958
1959
1960
1961
1962
1963
async def fetch_channel(self, channel_id: "Snowflake_Type") -> Optional["TYPE_ALL_CHANNEL"]:
    """
    Fetch a channel.

    !!! note
        This method is an alias for the cache which will either return a cached object, or query discord for the object
        if its not already cached.

    Args:
        channel_id: The ID of the channel to get

    Returns:
        Channel Object if found, otherwise None

    """
    try:
        return await self.cache.fetch_channel(channel_id)
    except NotFound:
        return None

get_channel(channel_id)

Get a channel.

Note

This method is an alias for the cache which will return a cached object.

Parameters:

Name Type Description Default
channel_id Snowflake_Type

The ID of the channel to get

required

Returns:

Type Description
Optional[TYPE_ALL_CHANNEL]

Channel Object if found, otherwise None

Source code in naff/client/client.py
1965
1966
1967
1968
1969
1970
1971
1972
1973
1974
1975
1976
1977
1978
1979
def get_channel(self, channel_id: "Snowflake_Type") -> Optional["TYPE_ALL_CHANNEL"]:
    """
    Get a channel.

    !!! note
        This method is an alias for the cache which will return a cached object.

    Args:
        channel_id: The ID of the channel to get

    Returns:
        Channel Object if found, otherwise None

    """
    return self.cache.get_channel(channel_id)

fetch_user(user_id) async

Fetch a user.

Note

This method is an alias for the cache which will either return a cached object, or query discord for the object if its not already cached.

Parameters:

Name Type Description Default
user_id Snowflake_Type

The ID of the user to get

required

Returns:

Type Description
Optional[User]

User Object if found, otherwise None

Source code in naff/client/client.py
1981
1982
1983
1984
1985
1986
1987
1988
1989
1990
1991
1992
1993
1994
1995
1996
1997
1998
1999
async def fetch_user(self, user_id: "Snowflake_Type") -> Optional[User]:
    """
    Fetch a user.

    !!! note
        This method is an alias for the cache which will either return a cached object, or query discord for the object
        if its not already cached.

    Args:
        user_id: The ID of the user to get

    Returns:
        User Object if found, otherwise None

    """
    try:
        return await self.cache.fetch_user(user_id)
    except NotFound:
        return None

get_user(user_id)

Get a user.

Note

This method is an alias for the cache which will return a cached object.

Parameters:

Name Type Description Default
user_id Snowflake_Type

The ID of the user to get

required

Returns:

Type Description
Optional[User]

User Object if found, otherwise None

Source code in naff/client/client.py
2001
2002
2003
2004
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
def get_user(self, user_id: "Snowflake_Type") -> Optional[User]:
    """
    Get a user.

    !!! note
        This method is an alias for the cache which will return a cached object.

    Args:
        user_id: The ID of the user to get

    Returns:
        User Object if found, otherwise None

    """
    return self.cache.get_user(user_id)

fetch_member(user_id, guild_id) async

Fetch a member from a guild.

Note

This method is an alias for the cache which will either return a cached object, or query discord for the object if its not already cached.

Parameters:

Name Type Description Default
user_id Snowflake_Type

The ID of the member

required
guild_id Snowflake_Type

The ID of the guild to get the member from

required

Returns:

Type Description
Optional[Member]

Member object if found, otherwise None

Source code in naff/client/client.py
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
2027
2028
2029
2030
2031
2032
2033
2034
2035
2036
async def fetch_member(self, user_id: "Snowflake_Type", guild_id: "Snowflake_Type") -> Optional[Member]:
    """
    Fetch a member from a guild.

    !!! note
        This method is an alias for the cache which will either return a cached object, or query discord for the object
        if its not already cached.

    Args:
        user_id: The ID of the member
        guild_id: The ID of the guild to get the member from

    Returns:
        Member object if found, otherwise None

    """
    try:
        return await self.cache.fetch_member(guild_id, user_id)
    except NotFound:
        return None

get_member(user_id, guild_id)

Get a member from a guild.

Note

This method is an alias for the cache which will return a cached object.

Parameters:

Name Type Description Default
user_id Snowflake_Type

The ID of the member

required
guild_id Snowflake_Type

The ID of the guild to get the member from

required

Returns:

Type Description
Optional[Member]

Member object if found, otherwise None

Source code in naff/client/client.py
2038
2039
2040
2041
2042
2043
2044
2045
2046
2047
2048
2049
2050
2051
2052
2053
def get_member(self, user_id: "Snowflake_Type", guild_id: "Snowflake_Type") -> Optional[Member]:
    """
    Get a member from a guild.

    !!! note
        This method is an alias for the cache which will return a cached object.

    Args:
        user_id: The ID of the member
        guild_id: The ID of the guild to get the member from

    Returns:
        Member object if found, otherwise None

    """
    return self.cache.get_member(guild_id, user_id)

fetch_scheduled_event(guild_id, scheduled_event_id, with_user_count=False) async

Fetch a scheduled event by id.

Parameters:

Name Type Description Default
guild_id Snowflake_Type

The ID of the guild to get the scheduled event from

required
scheduled_event_id Snowflake_Type

The ID of the scheduled event to get

required
with_user_count bool

Whether to include the user count in the response

False

Returns:

Type Description
Optional[ScheduledEvent]

The scheduled event if found, otherwise None

Source code in naff/client/client.py
2055
2056
2057
2058
2059
2060
2061
2062
2063
2064
2065
2066
2067
2068
2069
2070
2071
2072
2073
2074
async def fetch_scheduled_event(
    self, guild_id: "Snowflake_Type", scheduled_event_id: "Snowflake_Type", with_user_count: bool = False
) -> Optional["ScheduledEvent"]:
    """
    Fetch a scheduled event by id.

    Args:
        guild_id: The ID of the guild to get the scheduled event from
        scheduled_event_id: The ID of the scheduled event to get
        with_user_count: Whether to include the user count in the response

    Returns:
        The scheduled event if found, otherwise None

    """
    try:
        scheduled_event_data = await self.http.get_scheduled_event(guild_id, scheduled_event_id, with_user_count)
        return ScheduledEvent.from_dict(scheduled_event_data, self)
    except NotFound:
        return None

fetch_custom_emoji(emoji_id, guild_id) async

Fetch a custom emoji by id.

Parameters:

Name Type Description Default
emoji_id Snowflake_Type

The id of the custom emoji.

required
guild_id Snowflake_Type

The id of the guild the emoji belongs to.

required

Returns:

Type Description
Optional[CustomEmoji]

The custom emoji if found, otherwise None.

Source code in naff/client/client.py
2076
2077
2078
2079
2080
2081
2082
2083
2084
2085
2086
2087
2088
2089
2090
2091
async def fetch_custom_emoji(self, emoji_id: "Snowflake_Type", guild_id: "Snowflake_Type") -> Optional[CustomEmoji]:
    """
    Fetch a custom emoji by id.

    Args:
        emoji_id: The id of the custom emoji.
        guild_id: The id of the guild the emoji belongs to.

    Returns:
        The custom emoji if found, otherwise None.

    """
    try:
        return await self.cache.fetch_emoji(guild_id, emoji_id)
    except NotFound:
        return None

get_custom_emoji(emoji_id, guild_id=None)

Get a custom emoji by id.

Parameters:

Name Type Description Default
emoji_id Snowflake_Type

The id of the custom emoji.

required
guild_id Optional[Snowflake_Type]

The id of the guild the emoji belongs to.

None

Returns:

Type Description
Optional[CustomEmoji]

The custom emoji if found, otherwise None.

Source code in naff/client/client.py
2093
2094
2095
2096
2097
2098
2099
2100
2101
2102
2103
2104
2105
2106
2107
2108
2109
2110
def get_custom_emoji(
    self, emoji_id: "Snowflake_Type", guild_id: Optional["Snowflake_Type"] = None
) -> Optional[CustomEmoji]:
    """
    Get a custom emoji by id.

    Args:
        emoji_id: The id of the custom emoji.
        guild_id: The id of the guild the emoji belongs to.

    Returns:
        The custom emoji if found, otherwise None.

    """
    emoji = self.cache.get_emoji(emoji_id)
    if emoji and (not guild_id or emoji._guild_id == to_snowflake(guild_id)):
        return emoji
    return None

fetch_sticker(sticker_id) async

Fetch a sticker by ID.

Parameters:

Name Type Description Default
sticker_id Snowflake_Type

The ID of the sticker.

required

Returns:

Type Description
Optional[Sticker]

A sticker object if found, otherwise None

Source code in naff/client/client.py
2112
2113
2114
2115
2116
2117
2118
2119
2120
2121
2122
2123
2124
2125
2126
2127
async def fetch_sticker(self, sticker_id: "Snowflake_Type") -> Optional[Sticker]:
    """
    Fetch a sticker by ID.

    Args:
        sticker_id: The ID of the sticker.

    Returns:
        A sticker object if found, otherwise None

    """
    try:
        sticker_data = await self.http.get_sticker(sticker_id)
        return Sticker.from_dict(sticker_data, self)
    except NotFound:
        return None

fetch_nitro_packs() async

List the sticker packs available to Nitro subscribers.

Returns:

Type Description
Optional[List[StickerPack]]

A list of StickerPack objects if found, otherwise returns None

Source code in naff/client/client.py
2129
2130
2131
2132
2133
2134
2135
2136
2137
2138
2139
2140
2141
2142
async def fetch_nitro_packs(self) -> Optional[List["StickerPack"]]:
    """
    List the sticker packs available to Nitro subscribers.

    Returns:
        A list of StickerPack objects if found, otherwise returns None

    """
    try:
        packs_data = await self.http.list_nitro_sticker_packs()
        return [StickerPack.from_dict(data, self) for data in packs_data]

    except NotFound:
        return None

fetch_voice_regions() async

List the voice regions available on Discord.

Returns:

Type Description
List[VoiceRegion]

A list of voice regions.

Source code in naff/client/client.py
2144
2145
2146
2147
2148
2149
2150
2151
2152
2153
2154
async def fetch_voice_regions(self) -> List["VoiceRegion"]:
    """
    List the voice regions available on Discord.

    Returns:
        A list of voice regions.

    """
    regions_data = await self.http.list_voice_regions()
    regions = VoiceRegion.from_list(regions_data)
    return regions

connect_to_vc(guild_id, channel_id, muted=False, deafened=False) async

Connect the bot to a voice channel.

Parameters:

Name Type Description Default
guild_id Snowflake_Type

id of the guild the voice channel is in.

required
channel_id Snowflake_Type

id of the voice channel client wants to join.

required
muted bool

Whether the bot should be muted when connected.

False
deafened bool

Whether the bot should be deafened when connected.

False

Returns:

Type Description
ActiveVoiceState

The new active voice state on successfully connection.

Source code in naff/client/client.py
2156
2157
2158
2159
2160
2161
2162
2163
2164
2165
2166
2167
2168
2169
2170
2171
2172
async def connect_to_vc(
    self, guild_id: "Snowflake_Type", channel_id: "Snowflake_Type", muted: bool = False, deafened: bool = False
) -> ActiveVoiceState:
    """
    Connect the bot to a voice channel.

    Args:
        guild_id: id of the guild the voice channel is in.
        channel_id: id of the voice channel client wants to join.
        muted: Whether the bot should be muted when connected.
        deafened: Whether the bot should be deafened when connected.

    Returns:
        The new active voice state on successfully connection.

    """
    return await self._connection_state.voice_connect(guild_id, channel_id, muted, deafened)

get_bot_voice_state(guild_id)

Get the bot's voice state for a guild.

Parameters:

Name Type Description Default
guild_id Snowflake_Type

The target guild's id.

required

Returns:

Type Description
Optional[ActiveVoiceState]

The bot's voice state for the guild if connected, otherwise None.

Source code in naff/client/client.py
2174
2175
2176
2177
2178
2179
2180
2181
2182
2183
2184
2185
def get_bot_voice_state(self, guild_id: "Snowflake_Type") -> Optional[ActiveVoiceState]:
    """
    Get the bot's voice state for a guild.

    Args:
        guild_id: The target guild's id.

    Returns:
        The bot's voice state for the guild if connected, otherwise None.

    """
    return self._connection_state.get_voice_state(guild_id)

change_presence(status=Status.ONLINE, activity=None) async

Change the bots presence.

Parameters:

Name Type Description Default
status Optional[Union[str, Status]]

The status for the bot to be. i.e. online, afk, etc.

Status.ONLINE
activity Optional[Union[Activity, str]]

The activity for the bot to be displayed as doing.

None

Note

Bots may only be playing streaming listening watching or competing, other activity types are likely to fail.

Source code in naff/client/client.py
2187
2188
2189
2190
2191
2192
2193
2194
2195
2196
2197
2198
2199
2200
2201
async def change_presence(
    self, status: Optional[Union[str, Status]] = Status.ONLINE, activity: Optional[Union[Activity, str]] = None
) -> None:
    """
    Change the bots presence.

    Args:
        status: The status for the bot to be. i.e. online, afk, etc.
        activity: The activity for the bot to be displayed as doing.

    !!! note
        Bots may only be `playing` `streaming` `listening` `watching` or `competing`, other activity types are likely to fail.

    """
    await self._connection_state.change_presence(status, activity)

AutoShardedClient

Bases: Client

A client to automatically shard the bot.

You can optionally specify the total number of shards to start with, or it will be determined automatically.

Source code in naff/client/auto_shard_client.py
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
class AutoShardedClient(Client):
    """
    A client to automatically shard the bot.

    You can optionally specify the total number of shards to start with, or it will be determined automatically.
    """

    def __init__(self, *args, **kwargs) -> None:
        if "total_shards" not in kwargs:
            self.auto_sharding = True
        else:
            self.auto_sharding = False

        super().__init__(*args, **kwargs)

        self._connection_state = None

        self._connection_states: list[ConnectionState] = []

        self.max_start_concurrency: int = 1

    @property
    def gateway_started(self) -> bool:
        """Returns if the gateway has been started in all shards."""
        return all(state.gateway_started.is_set() for state in self._connection_states)

    @property
    def shards(self) -> list[ConnectionState]:
        """Returns a list of all shards currently in use."""
        return self._connection_states

    @property
    def latency(self) -> float:
        """The average latency of all active gateways."""
        if len(self._connection_states):
            latencies = sum((g.latency for g in self._connection_states))
            return latencies / len(self._connection_states)
        else:
            return float("inf")

    @property
    def latencies(self) -> dict[int, float]:
        """
        Return a dictionary of latencies for all shards.

        Returns:
            {shard_id: latency}
        """
        return {state.shard_id: state.latency for state in self._connection_states}

    async def stop(self) -> None:
        """Shutdown the bot."""
        logger.debug("Stopping the bot.")
        self._ready.clear()
        await self.http.close()
        await asyncio.gather(*(state.stop() for state in self._connection_states))

    def get_guild_websocket(self, guild_id: "Snowflake_Type") -> GatewayClient:
        """
        Get the appropriate websocket for a given guild

        Args:
            guild_id: The ID of the guild

        Returns:
            A gateway client for the given ID
        """
        shard_id = (int(guild_id) >> 22) % self.total_shards
        return next((state for state in self._connection_states if state.shard_id == shard_id), MISSING).gateway

    def get_shards_guild(self, shard_id: int) -> list[Guild]:
        """
        Returns the guilds that the specified shard can see

        Args:
            shard_id: The ID of the shard

        Returns:
            A list of guilds
        """
        return [guild for key, guild in self.cache.guild_cache.items() if ((key >> 22) % self.total_shards) == shard_id]

    @Listener.create()
    async def _on_websocket_ready(self, event: events.RawGatewayEvent) -> None:
        """
        Catches websocket ready and determines when to dispatch the client `READY` signal.

        Args:
            event: The websocket ready packet
        """
        connection_data = event.data
        expected_guilds = {to_snowflake(guild["id"]) for guild in connection_data["guilds"]}
        shard_id, total_shards = connection_data["shard"]
        connection_state = next((state for state in self._connection_states if state.shard_id == shard_id), None)

        if len(expected_guilds) != 0:
            while True:
                try:
                    await asyncio.wait_for(self._guild_event.wait(), self.guild_event_timeout)
                except asyncio.TimeoutError:
                    logger.warning("Timeout waiting for guilds cache: Not all guilds will be in cache")
                    break
                self._guild_event.clear()
                if all(self.cache.get_guild(g_id) is not None for g_id in expected_guilds):
                    # all guilds cached
                    break

            if self.fetch_members:
                logger.info(f"Shard {shard_id} is waiting for members to be chunked")
                await asyncio.gather(*(guild.chunked.wait() for guild in self.guilds if guild.id in expected_guilds))
        else:
            logger.warning(
                f"Shard {shard_id} reports it has 0 guilds, this is an indicator you may be using too many shards"
            )
        # noinspection PyProtectedMember
        connection_state._shard_ready.set()
        self.dispatch(ShardConnect(shard_id))
        logger.debug(f"Shard {shard_id} is now ready")

        # noinspection PyProtectedMember
        await asyncio.gather(*[shard._shard_ready.wait() for shard in self._connection_states])

        # run any pending startup tasks
        if self.async_startup_tasks:
            try:
                await asyncio.gather(*self.async_startup_tasks)
            except Exception as e:
                self.dispatch(events.Error("async-extension-loader", e))

        # cache slash commands
        if not self._startup:
            await self._init_interactions()

        if not self._ready.is_set():
            self._ready.set()
            if not self._startup:
                self._startup = True
                self.dispatch(events.Startup())
            self.dispatch(events.Ready())

    async def astart(self, token: str) -> None:
        """
        Asynchronous method to start the bot.

        Args:
            token: Your bot's token
        """
        logger.debug("Starting http client...")
        await self.login(token)

        tasks = []

        # Sort shards into their respective ratelimit buckets
        shard_buckets = defaultdict(list)
        for shard in self._connection_states:
            bucket = str(shard.shard_id % self.max_start_concurrency)
            shard_buckets[bucket].append(shard)

        for bucket in shard_buckets.values():
            for shard in bucket:
                logger.debug(f"Starting {shard.shard_id}")
                start = time.perf_counter()
                tasks.append(asyncio.create_task(shard.start()))

                if self.max_start_concurrency == 1:
                    # connection ratelimiting when discord has asked for one connection concurrently
                    # noinspection PyProtectedMember
                    await shard._shard_ready.wait()
                    await asyncio.sleep(5.1 - (time.perf_counter() - start))

            # wait for shards to finish starting
            # noinspection PyProtectedMember
            await asyncio.gather(*[shard._shard_ready.wait() for shard in self._connection_states])

        try:
            await asyncio.gather(*tasks)
        finally:
            await self.stop()

    async def login(self, token) -> None:
        """
        Login to discord via http.

        !!! note
            You will need to run Naff.start_gateway() before you start receiving gateway events.

        Args:
            token str: Your bot's token

        """
        await super().login(token)
        data = await self.http.get_gateway_bot()

        self.max_start_concurrency = data["session_start_limit"]["max_concurrency"]
        if self.auto_sharding:
            self.total_shards = data["shards"]
        elif data["shards"] != self.total_shards:
            recommended_shards = data["shards"]
            logger.info(
                f"Discord recommends you start with {recommended_shards} shard{'s' if recommended_shards != 1 else ''} instead of {self.total_shards}"
            )

        logger.debug(f"Starting bot with {self.total_shards} shard{'s' if self.total_shards != 1 else ''}")
        self._connection_states: list[ConnectionState] = [
            ConnectionState(self, self.intents, shard_id) for shard_id in range(self.total_shards)
        ]

gateway_started() property

Returns if the gateway has been started in all shards.

Source code in naff/client/auto_shard_client.py
46
47
48
49
@property
def gateway_started(self) -> bool:
    """Returns if the gateway has been started in all shards."""
    return all(state.gateway_started.is_set() for state in self._connection_states)

shards() property

Returns a list of all shards currently in use.

Source code in naff/client/auto_shard_client.py
51
52
53
54
@property
def shards(self) -> list[ConnectionState]:
    """Returns a list of all shards currently in use."""
    return self._connection_states

latency() property

The average latency of all active gateways.

Source code in naff/client/auto_shard_client.py
56
57
58
59
60
61
62
63
@property
def latency(self) -> float:
    """The average latency of all active gateways."""
    if len(self._connection_states):
        latencies = sum((g.latency for g in self._connection_states))
        return latencies / len(self._connection_states)
    else:
        return float("inf")

latencies() property

Return a dictionary of latencies for all shards.

Returns:

Type Description
dict[int, float]

{shard_id: latency}

Source code in naff/client/auto_shard_client.py
65
66
67
68
69
70
71
72
73
@property
def latencies(self) -> dict[int, float]:
    """
    Return a dictionary of latencies for all shards.

    Returns:
        {shard_id: latency}
    """
    return {state.shard_id: state.latency for state in self._connection_states}

stop() async

Shutdown the bot.

Source code in naff/client/auto_shard_client.py
75
76
77
78
79
80
async def stop(self) -> None:
    """Shutdown the bot."""
    logger.debug("Stopping the bot.")
    self._ready.clear()
    await self.http.close()
    await asyncio.gather(*(state.stop() for state in self._connection_states))

get_guild_websocket(guild_id)

Get the appropriate websocket for a given guild

Parameters:

Name Type Description Default
guild_id Snowflake_Type

The ID of the guild

required

Returns:

Type Description
GatewayClient

A gateway client for the given ID

Source code in naff/client/auto_shard_client.py
82
83
84
85
86
87
88
89
90
91
92
93
def get_guild_websocket(self, guild_id: "Snowflake_Type") -> GatewayClient:
    """
    Get the appropriate websocket for a given guild

    Args:
        guild_id: The ID of the guild

    Returns:
        A gateway client for the given ID
    """
    shard_id = (int(guild_id) >> 22) % self.total_shards
    return next((state for state in self._connection_states if state.shard_id == shard_id), MISSING).gateway

get_shards_guild(shard_id)

Returns the guilds that the specified shard can see

Parameters:

Name Type Description Default
shard_id int

The ID of the shard

required

Returns:

Type Description
list[Guild]

A list of guilds

Source code in naff/client/auto_shard_client.py
 95
 96
 97
 98
 99
100
101
102
103
104
105
def get_shards_guild(self, shard_id: int) -> list[Guild]:
    """
    Returns the guilds that the specified shard can see

    Args:
        shard_id: The ID of the shard

    Returns:
        A list of guilds
    """
    return [guild for key, guild in self.cache.guild_cache.items() if ((key >> 22) % self.total_shards) == shard_id]

astart(token) async

Asynchronous method to start the bot.

Parameters:

Name Type Description Default
token str

Your bot's token

required
Source code in naff/client/auto_shard_client.py
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
async def astart(self, token: str) -> None:
    """
    Asynchronous method to start the bot.

    Args:
        token: Your bot's token
    """
    logger.debug("Starting http client...")
    await self.login(token)

    tasks = []

    # Sort shards into their respective ratelimit buckets
    shard_buckets = defaultdict(list)
    for shard in self._connection_states:
        bucket = str(shard.shard_id % self.max_start_concurrency)
        shard_buckets[bucket].append(shard)

    for bucket in shard_buckets.values():
        for shard in bucket:
            logger.debug(f"Starting {shard.shard_id}")
            start = time.perf_counter()
            tasks.append(asyncio.create_task(shard.start()))

            if self.max_start_concurrency == 1:
                # connection ratelimiting when discord has asked for one connection concurrently
                # noinspection PyProtectedMember
                await shard._shard_ready.wait()
                await asyncio.sleep(5.1 - (time.perf_counter() - start))

        # wait for shards to finish starting
        # noinspection PyProtectedMember
        await asyncio.gather(*[shard._shard_ready.wait() for shard in self._connection_states])

    try:
        await asyncio.gather(*tasks)
    finally:
        await self.stop()

login(token) async

Login to discord via http.

Note

You will need to run Naff.start_gateway() before you start receiving gateway events.

Parameters:

Name Type Description Default
token str

Your bot's token

required
Source code in naff/client/auto_shard_client.py
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
async def login(self, token) -> None:
    """
    Login to discord via http.

    !!! note
        You will need to run Naff.start_gateway() before you start receiving gateway events.

    Args:
        token str: Your bot's token

    """
    await super().login(token)
    data = await self.http.get_gateway_bot()

    self.max_start_concurrency = data["session_start_limit"]["max_concurrency"]
    if self.auto_sharding:
        self.total_shards = data["shards"]
    elif data["shards"] != self.total_shards:
        recommended_shards = data["shards"]
        logger.info(
            f"Discord recommends you start with {recommended_shards} shard{'s' if recommended_shards != 1 else ''} instead of {self.total_shards}"
        )

    logger.debug(f"Starting bot with {self.total_shards} shard{'s' if self.total_shards != 1 else ''}")
    self._connection_states: list[ConnectionState] = [
        ConnectionState(self, self.intents, shard_id) for shard_id in range(self.total_shards)
    ]

ActiveVoiceState

Bases: VoiceState

Source code in naff/models/naff/active_voice_state.py
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
@define()
class ActiveVoiceState(VoiceState):
    ws: Optional[VoiceGateway] = field(default=None)
    """The websocket for this voice state"""
    player: Optional[Player] = field(default=None)
    """The playback task that broadcasts audio data to discord"""
    _volume: float = field(default=0.5)

    # standard voice states expect this data, this voice state lacks it initially; so we make them optional
    user_id: "Snowflake_Type" = field(default=MISSING, converter=optional(to_snowflake))
    _guild_id: Optional["Snowflake_Type"] = field(default=None, converter=optional(to_snowflake))
    _member_id: Optional["Snowflake_Type"] = field(default=None, converter=optional(to_snowflake))

    def __attrs_post_init__(self) -> None:
        # jank line to handle the two inherently incompatible data structures
        self._member_id = self.user_id = self._client.user.id

    def __del__(self) -> None:
        if self.connected:
            self.ws.close()
        if self.player:
            self.player.stop()

    def __repr__(self) -> str:
        return f"<ActiveVoiceState: channel={self.channel} guild={self.guild} volume={self.volume} playing={self.playing} audio={self.current_audio}>"

    @property
    def current_audio(self) -> Optional["BaseAudio"]:
        """The current audio being played"""
        if self.player:
            return self.player.current_audio

    @property
    def volume(self) -> float:
        """Get the volume of the player"""
        return self._volume

    @volume.setter
    def volume(self, value) -> None:
        """Set the volume of the player"""
        if value < 0.0:
            raise ValueError("Volume may not be negative.")
        self._volume = value
        if self.player and hasattr(self.player.current_audio, "volume"):
            self.player.current_audio.volume = value

    @property
    def paused(self) -> bool:
        """Is the player currently paused"""
        if self.player:
            return self.player.paused
        return False

    @property
    def playing(self) -> bool:
        """Are we currently playing something?"""
        # noinspection PyProtectedMember
        if not self.player or not self.current_audio or self.player.stopped or not self.player._resume.is_set():
            # if any of the above are truthy, we aren't playing
            return False
        return True

    @property
    def stopped(self) -> bool:
        """Is the player stopped?"""
        if self.player:
            return self.player.stopped
        return True

    @property
    def connected(self) -> bool:
        """Is this voice state currently connected?"""
        # noinspection PyProtectedMember
        if self.ws is None:
            return False
        return self.ws._closed.is_set()

    @property
    def gateway(self) -> "GatewayClient":
        return self._client.get_guild_websocket(self._guild_id)

    async def wait_for_stopped(self) -> None:
        """Wait for the player to stop playing."""
        if self.player:
            # noinspection PyProtectedMember
            await self.player._stopped.wait()

    async def _ws_connect(self) -> None:
        """Runs the voice gateway connection"""
        async with self.ws:
            try:
                await self.ws.run()
            finally:
                if self.playing:
                    await self.stop()

    async def ws_connect(self) -> None:
        """Connect to the voice gateway for this voice state"""
        self.ws = VoiceGateway(self._client._connection_state, self._voice_state.data, self._voice_server.data)

        asyncio.create_task(self._ws_connect())
        await self.ws.wait_until_ready()

    def _guild_predicate(self, event) -> bool:
        return int(event.data["guild_id"]) == self._guild_id

    async def connect(self, timeout: int = 5) -> None:
        """
        Establish the voice connection.

        Args:
            timeout: How long to wait for state and server information from discord

        Raises:
            VoiceAlreadyConnected: if the voice state is already connected to the voice channel
            VoiceConnectionTimeout: if the voice state fails to connect

        """
        if self.connected:
            raise VoiceAlreadyConnected
        await self.gateway.voice_state_update(self._guild_id, self._channel_id, self.self_mute, self.self_deaf)

        logger.debug("Waiting for voice connection data...")

        try:
            self._voice_state, self._voice_server = await asyncio.gather(
                self._client.wait_for("raw_voice_state_update", self._guild_predicate, timeout=timeout),
                self._client.wait_for("raw_voice_server_update", self._guild_predicate, timeout=timeout),
            )
        except asyncio.TimeoutError:
            raise VoiceConnectionTimeout from None

        logger.debug("Attempting to initialise voice gateway...")
        await self.ws_connect()

    async def disconnect(self) -> None:
        """Disconnect from the voice channel."""
        await self.gateway.voice_state_update(self._guild_id, None)

    async def move(self, channel: "Snowflake_Type", timeout: int = 5) -> None:
        """
        Move to another voice channel.

        Args:
            channel: The channel to move to
            timeout: How long to wait for state and server information from discord

        """
        target_channel = to_snowflake(channel)
        if target_channel != self._channel_id:
            already_paused = self.paused
            if self.player:
                self.player.pause()

            self._channel_id = target_channel
            await self.gateway.voice_state_update(self._guild_id, self._channel_id, self.self_mute, self.self_deaf)

            logger.debug("Waiting for voice connection data...")
            try:
                await self._client.wait_for("raw_voice_state_update", self._guild_predicate, timeout=timeout)
            except asyncio.TimeoutError:
                await self._close_connection()
                raise VoiceConnectionTimeout from None

            if self.player and not already_paused:
                self.player.resume()

    async def stop(self) -> None:
        """Stop playback."""
        self.player.stop()
        await self.player._stopped.wait()

    def pause(self) -> None:
        """Pause playback"""
        self.player.pause()

    def resume(self) -> None:
        """Resume playback."""
        self.player.resume()

    async def play(self, audio: "BaseAudio") -> None:
        """
        Start playing an audio object.

        Waits for the player to stop before returning.

        Args:
            audio: The audio object to play
        """
        if self.player:
            await self.stop()

        with Player(audio, self, asyncio.get_running_loop()) as self.player:
            self.player.play()
            await self.wait_for_stopped()

    def play_no_wait(self, audio: "BaseAudio") -> None:
        """
        Start playing an audio object, but don't wait for playback to finish.

        Args:
            audio: The audio object to play
        """
        asyncio.create_task(self.play(audio))

    async def _voice_server_update(self, data) -> None:
        """
        An internal receiver for voice server events.

        Args:
            data: voice server data
        """
        self.ws.set_new_voice_server(data)

    async def _voice_state_update(
        self, before: Optional[VoiceState], after: Optional[VoiceState], data: Optional[VoiceStateData]
    ) -> None:
        """
        An internal receiver for voice server state events.

        Args:
            before: The previous voice state
            after: The current voice state
            data: Raw data from gateway
        """
        if after is None:
            # bot disconnected
            logger.info(f"Disconnecting from voice channel {self._channel_id}")
            await self._close_connection()
            self._client.cache.delete_bot_voice_state(self._guild_id)
            return

        self.update_from_dict(data)

    async def _close_connection(self) -> None:
        """Close the voice connection."""
        if self.playing:
            await self.stop()
        if self.connected:
            self.ws.close()

ws: Optional[VoiceGateway] = field(default=None) class-attribute

The websocket for this voice state

player: Optional[Player] = field(default=None) class-attribute

The playback task that broadcasts audio data to discord

current_audio() property

The current audio being played

Source code in naff/models/naff/active_voice_state.py
48
49
50
51
52
@property
def current_audio(self) -> Optional["BaseAudio"]:
    """The current audio being played"""
    if self.player:
        return self.player.current_audio

volume() property writable

Get the volume of the player

Source code in naff/models/naff/active_voice_state.py
54
55
56
57
@property
def volume(self) -> float:
    """Get the volume of the player"""
    return self._volume

paused() property

Is the player currently paused

Source code in naff/models/naff/active_voice_state.py
68
69
70
71
72
73
@property
def paused(self) -> bool:
    """Is the player currently paused"""
    if self.player:
        return self.player.paused
    return False

playing() property

Are we currently playing something?

Source code in naff/models/naff/active_voice_state.py
75
76
77
78
79
80
81
82
@property
def playing(self) -> bool:
    """Are we currently playing something?"""
    # noinspection PyProtectedMember
    if not self.player or not self.current_audio or self.player.stopped or not self.player._resume.is_set():
        # if any of the above are truthy, we aren't playing
        return False
    return True

stopped() property

Is the player stopped?

Source code in naff/models/naff/active_voice_state.py
84
85
86
87
88
89
@property
def stopped(self) -> bool:
    """Is the player stopped?"""
    if self.player:
        return self.player.stopped
    return True

connected() property

Is this voice state currently connected?

Source code in naff/models/naff/active_voice_state.py
91
92
93
94
95
96
97
@property
def connected(self) -> bool:
    """Is this voice state currently connected?"""
    # noinspection PyProtectedMember
    if self.ws is None:
        return False
    return self.ws._closed.is_set()

wait_for_stopped() async

Wait for the player to stop playing.

Source code in naff/models/naff/active_voice_state.py
103
104
105
106
107
async def wait_for_stopped(self) -> None:
    """Wait for the player to stop playing."""
    if self.player:
        # noinspection PyProtectedMember
        await self.player._stopped.wait()

ws_connect() async

Connect to the voice gateway for this voice state

Source code in naff/models/naff/active_voice_state.py
118
119
120
121
122
123
async def ws_connect(self) -> None:
    """Connect to the voice gateway for this voice state"""
    self.ws = VoiceGateway(self._client._connection_state, self._voice_state.data, self._voice_server.data)

    asyncio.create_task(self._ws_connect())
    await self.ws.wait_until_ready()

connect(timeout=5) async

Establish the voice connection.

Parameters:

Name Type Description Default
timeout int

How long to wait for state and server information from discord

5

Raises:

Type Description
VoiceAlreadyConnected

if the voice state is already connected to the voice channel

VoiceConnectionTimeout

if the voice state fails to connect

Source code in naff/models/naff/active_voice_state.py
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
async def connect(self, timeout: int = 5) -> None:
    """
    Establish the voice connection.

    Args:
        timeout: How long to wait for state and server information from discord

    Raises:
        VoiceAlreadyConnected: if the voice state is already connected to the voice channel
        VoiceConnectionTimeout: if the voice state fails to connect

    """
    if self.connected:
        raise VoiceAlreadyConnected
    await self.gateway.voice_state_update(self._guild_id, self._channel_id, self.self_mute, self.self_deaf)

    logger.debug("Waiting for voice connection data...")

    try:
        self._voice_state, self._voice_server = await asyncio.gather(
            self._client.wait_for("raw_voice_state_update", self._guild_predicate, timeout=timeout),
            self._client.wait_for("raw_voice_server_update", self._guild_predicate, timeout=timeout),
        )
    except asyncio.TimeoutError:
        raise VoiceConnectionTimeout from None

    logger.debug("Attempting to initialise voice gateway...")
    await self.ws_connect()

disconnect() async

Disconnect from the voice channel.

Source code in naff/models/naff/active_voice_state.py
157
158
159
async def disconnect(self) -> None:
    """Disconnect from the voice channel."""
    await self.gateway.voice_state_update(self._guild_id, None)

move(channel, timeout=5) async

Move to another voice channel.

Parameters:

Name Type Description Default
channel Snowflake_Type

The channel to move to

required
timeout int

How long to wait for state and server information from discord

5
Source code in naff/models/naff/active_voice_state.py
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
async def move(self, channel: "Snowflake_Type", timeout: int = 5) -> None:
    """
    Move to another voice channel.

    Args:
        channel: The channel to move to
        timeout: How long to wait for state and server information from discord

    """
    target_channel = to_snowflake(channel)
    if target_channel != self._channel_id:
        already_paused = self.paused
        if self.player:
            self.player.pause()

        self._channel_id = target_channel
        await self.gateway.voice_state_update(self._guild_id, self._channel_id, self.self_mute, self.self_deaf)

        logger.debug("Waiting for voice connection data...")
        try:
            await self._client.wait_for("raw_voice_state_update", self._guild_predicate, timeout=timeout)
        except asyncio.TimeoutError:
            await self._close_connection()
            raise VoiceConnectionTimeout from None

        if self.player and not already_paused:
            self.player.resume()

stop() async

Stop playback.

Source code in naff/models/naff/active_voice_state.py
189
190
191
192
async def stop(self) -> None:
    """Stop playback."""
    self.player.stop()
    await self.player._stopped.wait()

pause()

Pause playback

Source code in naff/models/naff/active_voice_state.py
194
195
196
def pause(self) -> None:
    """Pause playback"""
    self.player.pause()

resume()

Resume playback.

Source code in naff/models/naff/active_voice_state.py
198
199
200
def resume(self) -> None:
    """Resume playback."""
    self.player.resume()

play(audio) async

Start playing an audio object.

Waits for the player to stop before returning.

Parameters:

Name Type Description Default
audio BaseAudio

The audio object to play

required
Source code in naff/models/naff/active_voice_state.py
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
async def play(self, audio: "BaseAudio") -> None:
    """
    Start playing an audio object.

    Waits for the player to stop before returning.

    Args:
        audio: The audio object to play
    """
    if self.player:
        await self.stop()

    with Player(audio, self, asyncio.get_running_loop()) as self.player:
        self.player.play()
        await self.wait_for_stopped()

play_no_wait(audio)

Start playing an audio object, but don't wait for playback to finish.

Parameters:

Name Type Description Default
audio BaseAudio

The audio object to play

required
Source code in naff/models/naff/active_voice_state.py
218
219
220
221
222
223
224
225
def play_no_wait(self, audio: "BaseAudio") -> None:
    """
    Start playing an audio object, but don't wait for playback to finish.

    Args:
        audio: The audio object to play
    """
    asyncio.create_task(self.play(audio))

User

BaseUser

Bases: DiscordObject, _SendDMMixin

Base class for User, essentially partial user discord model.

Source code in naff/models/discord/user.py
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
@define()
class BaseUser(DiscordObject, _SendDMMixin):
    """Base class for User, essentially partial user discord model."""

    username: str = field(repr=True, metadata=docs("The user's username, not unique across the platform"))
    discriminator: int = field(repr=True, metadata=docs("The user's 4-digit discord-tag"))
    avatar: "Asset" = field(metadata=docs("The user's default avatar"))

    def __str__(self) -> str:
        return self.tag

    @classmethod
    def _process_dict(cls, data: Dict[str, Any], client: "Client") -> Dict[str, Any]:
        if not isinstance(data["avatar"], Asset):
            if data["avatar"]:
                data["avatar"] = Asset.from_path_hash(client, f"avatars/{data['id']}/{{}}", data["avatar"])
            else:
                data["avatar"] = Asset(client, f"{Asset.BASE}/embed/avatars/{int(data['discriminator']) % 5}")
        return data

    @property
    def tag(self) -> str:
        """Returns the user's Discord tag."""
        return f"{self.username}#{self.discriminator}"

    @property
    def mention(self) -> str:
        """Returns a string that would mention the user."""
        return f"<@{self.id}>"

    @property
    def display_name(self) -> str:
        """The users display name, will return nickname if one is set, otherwise will return username."""
        return self.username  # for duck-typing compatibility with Member

    @property
    def display_avatar(self) -> "Asset":
        """The users displayed avatar, will return `guild_avatar` if one is set, otherwise will return user avatar."""
        return self.avatar

    async def fetch_dm(self) -> "DM":
        """Fetch the DM channel associated with this user."""
        return await self._client.cache.fetch_dm_channel(self.id)  # noqa

    def get_dm(self) -> Optional["DM"]:
        """Get the DM channel associated with this user."""
        return self._client.cache.get_dm_channel(self.id)  # noqa

    @property
    def mutual_guilds(self) -> List["Guild"]:
        """
        Get a list of mutual guilds shared between this user and the client.

        !!! note
            This will only be accurate if the guild members are cached internally
        """
        return [
            guild for guild in self._client.guilds if self._client.cache.get_member(guild_id=guild.id, user_id=self.id)
        ]

tag() property

Returns the user's Discord tag.

Source code in naff/models/discord/user.py
64
65
66
67
@property
def tag(self) -> str:
    """Returns the user's Discord tag."""
    return f"{self.username}#{self.discriminator}"

mention() property

Returns a string that would mention the user.

Source code in naff/models/discord/user.py
69
70
71
72
@property
def mention(self) -> str:
    """Returns a string that would mention the user."""
    return f"<@{self.id}>"

display_name() property

The users display name, will return nickname if one is set, otherwise will return username.

Source code in naff/models/discord/user.py
74
75
76
77
@property
def display_name(self) -> str:
    """The users display name, will return nickname if one is set, otherwise will return username."""
    return self.username  # for duck-typing compatibility with Member

display_avatar() property

The users displayed avatar, will return guild_avatar if one is set, otherwise will return user avatar.

Source code in naff/models/discord/user.py
79
80
81
82
@property
def display_avatar(self) -> "Asset":
    """The users displayed avatar, will return `guild_avatar` if one is set, otherwise will return user avatar."""
    return self.avatar

fetch_dm() async

Fetch the DM channel associated with this user.

Source code in naff/models/discord/user.py
84
85
86
async def fetch_dm(self) -> "DM":
    """Fetch the DM channel associated with this user."""
    return await self._client.cache.fetch_dm_channel(self.id)  # noqa

get_dm()

Get the DM channel associated with this user.

Source code in naff/models/discord/user.py
88
89
90
def get_dm(self) -> Optional["DM"]:
    """Get the DM channel associated with this user."""
    return self._client.cache.get_dm_channel(self.id)  # noqa

mutual_guilds() property

Get a list of mutual guilds shared between this user and the client.

Note

This will only be accurate if the guild members are cached internally

Source code in naff/models/discord/user.py
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
@property
def mutual_guilds(self) -> List["Guild"]:
    """
    Get a list of mutual guilds shared between this user and the client.

    !!! note
        This will only be accurate if the guild members are cached internally
    """
    return [
        guild for guild in self._client.guilds if self._client.cache.get_member(guild_id=guild.id, user_id=self.id)
    ]

User

Bases: BaseUser

Source code in naff/models/discord/user.py
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
@define()
class User(BaseUser):
    bot: bool = field(repr=True, default=False, metadata=docs("Is this user a bot?"))
    system: bool = field(
        default=False,
        metadata=docs("whether the user is an Official Discord System user (part of the urgent message system)"),
    )
    public_flags: "UserFlags" = field(
        repr=True, default=0, converter=UserFlags, metadata=docs("The flags associated with this user")
    )
    premium_type: "PremiumTypes" = field(
        default=0, converter=PremiumTypes, metadata=docs("The type of nitro subscription on a user's account")
    )

    banner: Optional["Asset"] = field(default=None, metadata=docs("The user's banner"))
    accent_color: Optional["Color"] = field(
        default=None,
        converter=optional_c(Color),
        metadata=docs("The user's banner color"),
    )
    activities: list[Activity] = field(
        factory=list,
        converter=list_converter(optional(Activity.from_dict)),
        metadata=docs("A list of activities the user is in"),
    )
    status: Absent[Status] = field(default=MISSING, metadata=docs("The user's status"), converter=optional(Status))

    @classmethod
    def _process_dict(cls, data: Dict[str, Any], client: "Client") -> Dict[str, Any]:
        data = super()._process_dict(data, client)
        if "banner" in data:
            data["banner"] = Asset.from_path_hash(client, f"banners/{data['id']}/{{}}", data["banner"])

        if data.get("premium_type", None) is None:
            data["premium_type"] = 0

        return data

    @property
    def member_instances(self) -> List["Member"]:
        """
        Returns the member object for all guilds both the bot and the user are in.

        !!! note
            This will only be accurate if the guild members are cached internally
        """
        member_objs = [
            self._client.cache.get_member(guild_id=guild.id, user_id=self.id) for guild in self._client.guilds
        ]
        return [member for member in member_objs if member]

member_instances() property

Returns the member object for all guilds both the bot and the user are in.

Note

This will only be accurate if the guild members are cached internally

Source code in naff/models/discord/user.py
143
144
145
146
147
148
149
150
151
152
153
154
@property
def member_instances(self) -> List["Member"]:
    """
    Returns the member object for all guilds both the bot and the user are in.

    !!! note
        This will only be accurate if the guild members are cached internally
    """
    member_objs = [
        self._client.cache.get_member(guild_id=guild.id, user_id=self.id) for guild in self._client.guilds
    ]
    return [member for member in member_objs if member]

NaffUser

Bases: User

Source code in naff/models/discord/user.py
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
@define()
class NaffUser(User):
    verified: bool = field(repr=True, metadata={"docs": "Whether the email on this account has been verified"})
    mfa_enabled: bool = field(
        default=False, metadata={"docs": "Whether the user has two factor enabled on their account"}
    )
    email: Optional[str] = field(default=None, metadata={"docs": "the user's email"})  # needs special permissions?
    locale: Optional[str] = field(default=None, metadata={"docs": "the user's chosen language option"})
    bio: Optional[str] = field(default=None, metadata={"docs": ""})
    flags: "UserFlags" = field(default=0, converter=UserFlags, metadata={"docs": "the flags on a user's account"})

    _guild_ids: Set["Snowflake_Type"] = field(factory=set, metadata={"docs": "All the guilds the user is in"})

    def _add_guilds(self, guild_ids: Set["Snowflake_Type"]) -> None:
        """
        Add the guilds that the user is in to the internal reference.

        Args:
            guild_ids: The guild ids to add

        """
        self._guild_ids |= guild_ids

    @property
    def guilds(self) -> List["Guild"]:
        """The guilds the user is in."""
        return [self._client.cache.get_guild(g_id) for g_id in self._guild_ids]

    async def edit(self, username: Absent[str] = MISSING, avatar: Absent[UPLOADABLE_TYPE] = MISSING) -> None:
        """
        Edit the client's user.

        You can either change the username, or avatar, or both at once.
        `avatar` may be set to `None` to remove your bot's avatar

        ??? Hint "Example Usage:"
            ```python
            await self.user.edit(avatar="path_to_file")
            ```
            or
            ```python
            await self.user.edit(username="hello world")
            ```

        Args:
            username: The username you want to use
            avatar: The avatar to use. Can be a image file, path, or `bytes` (see example)

        Raises:
            TooManyChanges: If you change the profile too many times

        """
        payload = {}
        if username:
            payload["username"] = username
        if avatar:
            payload["avatar"] = to_image_data(avatar)
        elif avatar is None:
            payload["avatar"] = None

        try:
            resp = await self._client.http.modify_client_user(payload)
        except HTTPException:
            raise TooManyChanges(
                "You have changed your profile too frequently, you need to wait a while before trying again."
            ) from None
        if resp:
            self._client.cache.place_user_data(resp)

guilds() property

The guilds the user is in.

Source code in naff/models/discord/user.py
180
181
182
183
@property
def guilds(self) -> List["Guild"]:
    """The guilds the user is in."""
    return [self._client.cache.get_guild(g_id) for g_id in self._guild_ids]

edit(username=MISSING, avatar=MISSING) async

Edit the client's user.

You can either change the username, or avatar, or both at once. avatar may be set to None to remove your bot's avatar

Example Usage:

1
await self.user.edit(avatar="path_to_file")
or
1
await self.user.edit(username="hello world")

Parameters:

Name Type Description Default
username Absent[str]

The username you want to use

MISSING
avatar Absent[UPLOADABLE_TYPE]

The avatar to use. Can be a image file, path, or bytes (see example)

MISSING

Raises:

Type Description
TooManyChanges

If you change the profile too many times

Source code in naff/models/discord/user.py
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
async def edit(self, username: Absent[str] = MISSING, avatar: Absent[UPLOADABLE_TYPE] = MISSING) -> None:
    """
    Edit the client's user.

    You can either change the username, or avatar, or both at once.
    `avatar` may be set to `None` to remove your bot's avatar

    ??? Hint "Example Usage:"
        ```python
        await self.user.edit(avatar="path_to_file")
        ```
        or
        ```python
        await self.user.edit(username="hello world")
        ```

    Args:
        username: The username you want to use
        avatar: The avatar to use. Can be a image file, path, or `bytes` (see example)

    Raises:
        TooManyChanges: If you change the profile too many times

    """
    payload = {}
    if username:
        payload["username"] = username
    if avatar:
        payload["avatar"] = to_image_data(avatar)
    elif avatar is None:
        payload["avatar"] = None

    try:
        resp = await self._client.http.modify_client_user(payload)
    except HTTPException:
        raise TooManyChanges(
            "You have changed your profile too frequently, you need to wait a while before trying again."
        ) from None
    if resp:
        self._client.cache.place_user_data(resp)

Member

Bases: DiscordObject, _SendDMMixin

Source code in naff/models/discord/user.py
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
@define()
class Member(DiscordObject, _SendDMMixin):
    bot: bool = field(repr=True, default=False, metadata=docs("Is this user a bot?"))
    nick: Optional[str] = field(repr=True, default=None, metadata=docs("The user's nickname in this guild'"))
    deaf: bool = field(default=False, metadata=docs("Has this user been deafened in voice channels?"))
    mute: bool = field(default=False, metadata=docs("Has this user been muted in voice channels?"))
    joined_at: "Timestamp" = field(converter=timestamp_converter, metadata=docs("When the user joined this guild"))
    premium_since: Optional["Timestamp"] = field(
        default=None,
        converter=optional_c(timestamp_converter),
        metadata=docs("When the user started boosting the guild"),
    )
    pending: Optional[bool] = field(
        default=None, metadata=docs("Whether the user has **not** passed guild's membership screening requirements")
    )
    guild_avatar: "Asset" = field(default=None, metadata=docs("The user's guild avatar"))
    communication_disabled_until: Optional["Timestamp"] = field(
        default=None,
        converter=optional_c(timestamp_converter),
        metadata=docs("When a member's timeout will expire, `None` or a time in the past if the user is not timed out"),
    )

    _guild_id: "Snowflake_Type" = field(repr=True, metadata=docs("The ID of the guild"))
    _role_ids: List["Snowflake_Type"] = field(
        factory=list, converter=list_converter(to_snowflake), metadata=docs("The roles IDs this user has")
    )

    @classmethod
    def _process_dict(cls, data: Dict[str, Any], client: "Client") -> Dict[str, Any]:
        if "user" in data:
            user_data = data.pop("user")
            client.cache.place_user_data(user_data)
            data["id"] = user_data["id"]
            data["bot"] = user_data.get("bot", False)
        elif "member" in data:
            member_data = data.pop("member")
            client.cache.place_user_data(data)
            member_data["id"] = data["id"]
            member_data["bot"] = data.get("bot", False)
            if "guild_id" not in member_data:
                member_data["guild_id"] = data.get("guild_id")
            data = member_data
        if data.get("avatar"):
            try:
                data["guild_avatar"] = Asset.from_path_hash(
                    client, f"guilds/{data['guild_id']}/users/{data['id']}/avatars/{{}}", data.pop("avatar", None)
                )
            except Exception as e:
                logger.warning(
                    f"[DEBUG NEEDED - REPORT THIS] Incomplete dictionary has been passed to member object: {e}"
                )
                raise

        data["role_ids"] = data.pop("roles", [])

        return data

    def update_from_dict(self, data) -> None:
        if "guild_id" not in data:
            data["guild_id"] = self._guild_id
        data["_role_ids"] = data.pop("roles", [])
        return super().update_from_dict(data)

    @property
    def user(self) -> "User":
        """Returns this member's user object."""
        return self._client.cache.get_user(self.id)

    def __str__(self) -> str:
        return self.user.tag

    def __getattr__(self, name: str) -> Any:
        # this allows for transparent access to user attributes
        try:
            return getattr(self.user, name)
        except AttributeError as e:
            raise AttributeError(f"Neither `User` or `Member` have attribute {name}") from e

    @property
    def nickname(self) -> str:
        """Alias for nick."""
        return self.nick

    @nickname.setter
    def nickname(self, nickname: str) -> None:
        """Sets the member's nickname."""
        self.nick = nickname

    @property
    def guild(self) -> "Guild":
        """The guild object this member is from."""
        return self._client.cache.get_guild(self._guild_id)

    @property
    def roles(self) -> List["Role"]:
        """The roles this member has."""
        return [r for r in self.guild.roles if r.id in self._role_ids]

    @property
    def top_role(self) -> "Role":
        """The member's top most role."""
        return max(self.roles, key=lambda x: x.position) if self.roles else self.guild.default_role

    @property
    def display_name(self) -> str:
        """The users display name, will return nickname if one is set, otherwise will return username."""
        return self.nickname or self.username

    @property
    def display_avatar(self) -> "Asset":
        """The users displayed avatar, will return `guild_avatar` if one is set, otherwise will return user avatar."""
        return self.guild_avatar or self.user.avatar

    @property
    def premium(self) -> bool:
        """Is this member a server booster?"""
        return self.premium_since is not None

    @property
    def guild_permissions(self) -> Permissions:
        """
        Returns the permissions this member has in the guild.

        Returns:
            Permission data

        """
        guild = self.guild
        if guild.is_owner(self):
            return Permissions.ALL

        permissions = guild.default_role.permissions  # get @everyone role

        for role in self.roles:
            permissions |= role.permissions

        if Permissions.ADMINISTRATOR in permissions:
            return Permissions.ALL

        return permissions

    @property
    def voice(self) -> Optional["VoiceState"]:
        """Returns the voice state of this user if any."""
        return self._client.cache.get_voice_state(self.id)

    def has_permission(self, *permissions: Permissions) -> bool:
        """
        Checks if the member has all the given permission(s).

        ??? Hint "Example Usage:"
            Two different styles can be used to call this method.

            ```python
            member.has_permission(Permissions.KICK_MEMBERS, Permissions.BAN_MEMBERS)
            ```
            or
            ```python
            member.has_permission(Permissions.KICK_MEMBERS | Permissions.BAN_MEMBERS)
            ```

            If `member` has both permissions, `True` gets returned.

        Args:
            *permissions: The permission(s) to check whether the user has it.

        """
        # Get the user's permissions
        guild_permissions = self.guild_permissions

        # Check all permissions separately
        for permission in permissions:
            if permission not in guild_permissions:
                return False
        return True

    def channel_permissions(self, channel: "TYPE_GUILD_CHANNEL") -> Permissions:
        """
        Returns the permissions this member has in a channel.

        Args:
            channel: The channel in question

        Returns:
            Permissions data

        ??? note
            This method is used in `Channel.permissions_for`

        """
        permissions = self.guild_permissions

        if Permissions.ADMINISTRATOR in permissions:
            return Permissions.ALL

        overwrites = tuple(
            filter(
                lambda overwrite: overwrite.id in (self._guild_id, self.id, *self._role_ids),
                channel.permission_overwrites,
            )
        )

        for everyone_overwrite in filter(lambda overwrite: overwrite.id == self._guild_id, overwrites):
            permissions &= ~everyone_overwrite.deny
            permissions |= everyone_overwrite.allow

        for role_overwrite in filter(lambda overwrite: overwrite.id not in (self._guild_id, self.id), overwrites):
            permissions &= ~role_overwrite.deny
            permissions |= role_overwrite.allow

        for member_overwrite in filter(lambda overwrite: overwrite.id == self.id, overwrites):
            permissions &= ~member_overwrite.deny
            permissions |= member_overwrite.allow

        return permissions

    async def edit_nickname(self, new_nickname: Absent[str] = MISSING, reason: Absent[str] = MISSING) -> None:
        """
        Change the user's nickname.

        Args:
            new_nickname: The new nickname to apply
            reason: The reason for this change

        !!! note
            Leave `new_nickname` empty to clean user's nickname

        """
        if self.id == self._client.user.id:
            await self._client.http.modify_current_member(self._guild_id, nickname=new_nickname, reason=reason)
        else:
            await self._client.http.modify_guild_member(self._guild_id, self.id, nickname=new_nickname, reason=reason)

    async def add_role(self, role: Union[Snowflake_Type, Role], reason: Absent[str] = MISSING) -> None:
        """
        Add a role to this member.

        Args:
            role: The role to add
            reason: The reason for adding this role

        """
        role = to_snowflake(role)
        await self._client.http.add_guild_member_role(self._guild_id, self.id, role, reason=reason)
        self._role_ids.append(role)

    async def add_roles(self, roles: Iterable[Union[Snowflake_Type, Role]], reason: Absent[str] = MISSING) -> None:
        """
        Atomically add multiple roles to this member.

        Args:
            roles: The roles to add
            reason: The reason for adding the roles

        """
        new_roles = set(self._role_ids) | {to_snowflake(r) for r in roles}
        await self.edit(roles=new_roles, reason=reason)

    async def remove_role(self, role: Union[Snowflake_Type, Role], reason: Absent[str] = MISSING) -> None:
        """
        Remove a role from this user.

        Args:
            role: The role to remove
            reason: The reason for this removal

        """
        role = to_snowflake(role)
        await self._client.http.remove_guild_member_role(self._guild_id, self.id, role, reason=reason)
        try:
            self._role_ids.remove(role)
        except ValueError:
            pass

    async def remove_roles(self, roles: Iterable[Union[Snowflake_Type, Role]], reason: Absent[str] = MISSING) -> None:
        """
        Atomically remove multiple roles from this member.

        Args:
            roles: The roles to remove
            reason: The reason for removing the roles

        """
        new_roles = set(self._role_ids) - {to_snowflake(r) for r in roles}
        await self.edit(roles=new_roles, reason=reason)

    def has_role(self, *roles: Union[Snowflake_Type, Role]) -> bool:
        """
        Checks if the user has the given role(s).

        Args:
            *roles: The role(s) to check whether the user has it.

        """
        return all(to_snowflake(role) in self._role_ids for role in roles)

    async def timeout(
        self,
        communication_disabled_until: Union["Timestamp", datetime, int, float, str, None],
        reason: Absent[str] = MISSING,
    ) -> dict:
        """
        Disable a members communication for a given time.

        Args:
            communication_disabled_until: The time until the user can communicate again
            reason: The reason for this timeout

        """
        if isinstance(communication_disabled_until, (datetime, int, float, str)):
            communication_disabled_until = timestamp_converter(communication_disabled_until)

        self.communication_disabled_until = communication_disabled_until

        return await self._client.http.modify_guild_member(
            self._guild_id,
            self.id,
            communication_disabled_until=communication_disabled_until,
            reason=reason,
        )

    async def move(self, channel_id: "Snowflake_Type") -> None:
        """
        Moves the member to a different voice channel.

        Args:
            channel_id: The voice channel to move the member to

        """
        await self._client.http.modify_guild_member(self._guild_id, self.id, channel_id=channel_id)

    async def edit(
        self,
        *,
        nickname: Absent[str] = MISSING,
        roles: Absent[Iterable["Snowflake_Type"]] = MISSING,
        mute: Absent[bool] = MISSING,
        deaf: Absent[bool] = MISSING,
        channel_id: Absent["Snowflake_Type"] = MISSING,
        communication_disabled_until: Absent[Union["Timestamp", None]] = MISSING,
        reason: Absent[str] = MISSING,
    ) -> None:
        """
        Modify attrbutes of this guild member.

        Args:
            nickname: Value to set users nickname to
            roles: Array of role ids the member is assigned
            mute: Whether the user is muted in voice channels. Will throw a 400 if the user is not in a voice channel
            deaf: Whether the user is deafened in voice channels
            channel_id: id of channel to move user to (if they are connected to voice)
            communication_disabled_until: 	when the user's timeout will expire and the user will be able to communicate in the guild again
            reason: An optional reason for the audit log
        """
        await self._client.http.modify_guild_member(
            self._guild_id,
            self.id,
            nickname=nickname,
            roles=roles,
            mute=mute,
            deaf=deaf,
            channel_id=channel_id,
            communication_disabled_until=communication_disabled_until,
            reason=reason,
        )

    async def kick(self, reason: Absent[str] = MISSING) -> None:
        """
        Remove a member from the guild.

        Args:
            reason: The reason for this removal

        """
        await self._client.http.remove_guild_member(self._guild_id, self.id, reason=reason)

    async def ban(
        self, delete_message_days: Absent[int] = MISSING, delete_message_seconds: int = 0, reason: Absent[str] = MISSING
    ) -> None:
        """
        Ban a member from the guild.

        Args:
            delete_message_days: (deprecated) The number of days of messages to delete
            delete_message_seconds: The number of seconds of messages to delete
            reason: The reason for this ban

        """
        if delete_message_days is not MISSING:
            warn("delete_message_days  is deprecated and will be removed in a future update", DeprecationWarning)
            delete_message_seconds = delete_message_days * 3600
        await self._client.http.create_guild_ban(self._guild_id, self.id, delete_message_seconds, reason=reason)

user() property

Returns this member's user object.

Source code in naff/models/discord/user.py
290
291
292
293
@property
def user(self) -> "User":
    """Returns this member's user object."""
    return self._client.cache.get_user(self.id)

nickname() property writable

Alias for nick.

Source code in naff/models/discord/user.py
305
306
307
308
@property
def nickname(self) -> str:
    """Alias for nick."""
    return self.nick

guild() property

The guild object this member is from.

Source code in naff/models/discord/user.py
315
316
317
318
@property
def guild(self) -> "Guild":
    """The guild object this member is from."""
    return self._client.cache.get_guild(self._guild_id)

roles() property

The roles this member has.

Source code in naff/models/discord/user.py
320
321
322
323
@property
def roles(self) -> List["Role"]:
    """The roles this member has."""
    return [r for r in self.guild.roles if r.id in self._role_ids]

top_role() property

The member's top most role.

Source code in naff/models/discord/user.py
325
326
327
328
@property
def top_role(self) -> "Role":
    """The member's top most role."""
    return max(self.roles, key=lambda x: x.position) if self.roles else self.guild.default_role

display_name() property

The users display name, will return nickname if one is set, otherwise will return username.

Source code in naff/models/discord/user.py
330
331
332
333
@property
def display_name(self) -> str:
    """The users display name, will return nickname if one is set, otherwise will return username."""
    return self.nickname or self.username

display_avatar() property

The users displayed avatar, will return guild_avatar if one is set, otherwise will return user avatar.

Source code in naff/models/discord/user.py
335
336
337
338
@property
def display_avatar(self) -> "Asset":
    """The users displayed avatar, will return `guild_avatar` if one is set, otherwise will return user avatar."""
    return self.guild_avatar or self.user.avatar

premium() property

Is this member a server booster?

Source code in naff/models/discord/user.py
340
341
342
343
@property
def premium(self) -> bool:
    """Is this member a server booster?"""
    return self.premium_since is not None

guild_permissions() property

Returns the permissions this member has in the guild.

Returns:

Type Description
Permissions

Permission data

Source code in naff/models/discord/user.py
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
@property
def guild_permissions(self) -> Permissions:
    """
    Returns the permissions this member has in the guild.

    Returns:
        Permission data

    """
    guild = self.guild
    if guild.is_owner(self):
        return Permissions.ALL

    permissions = guild.default_role.permissions  # get @everyone role

    for role in self.roles:
        permissions |= role.permissions

    if Permissions.ADMINISTRATOR in permissions:
        return Permissions.ALL

    return permissions

voice() property

Returns the voice state of this user if any.

Source code in naff/models/discord/user.py
368
369
370
371
@property
def voice(self) -> Optional["VoiceState"]:
    """Returns the voice state of this user if any."""
    return self._client.cache.get_voice_state(self.id)

has_permission(*permissions)

Checks if the member has all the given permission(s).

Example Usage:

Two different styles can be used to call this method.

1
member.has_permission(Permissions.KICK_MEMBERS, Permissions.BAN_MEMBERS)
or
1
member.has_permission(Permissions.KICK_MEMBERS | Permissions.BAN_MEMBERS)

If member has both permissions, True gets returned.

Parameters:

Name Type Description Default
*permissions Permissions

The permission(s) to check whether the user has it.

()
Source code in naff/models/discord/user.py
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
def has_permission(self, *permissions: Permissions) -> bool:
    """
    Checks if the member has all the given permission(s).

    ??? Hint "Example Usage:"
        Two different styles can be used to call this method.

        ```python
        member.has_permission(Permissions.KICK_MEMBERS, Permissions.BAN_MEMBERS)
        ```
        or
        ```python
        member.has_permission(Permissions.KICK_MEMBERS | Permissions.BAN_MEMBERS)
        ```

        If `member` has both permissions, `True` gets returned.

    Args:
        *permissions: The permission(s) to check whether the user has it.

    """
    # Get the user's permissions
    guild_permissions = self.guild_permissions

    # Check all permissions separately
    for permission in permissions:
        if permission not in guild_permissions:
            return False
    return True

channel_permissions(channel)

Returns the permissions this member has in a channel.

Parameters:

Name Type Description Default
channel TYPE_GUILD_CHANNEL

The channel in question

required

Returns:

Type Description
Permissions

Permissions data

Note

This method is used in Channel.permissions_for

Source code in naff/models/discord/user.py
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
def channel_permissions(self, channel: "TYPE_GUILD_CHANNEL") -> Permissions:
    """
    Returns the permissions this member has in a channel.

    Args:
        channel: The channel in question

    Returns:
        Permissions data

    ??? note
        This method is used in `Channel.permissions_for`

    """
    permissions = self.guild_permissions

    if Permissions.ADMINISTRATOR in permissions:
        return Permissions.ALL

    overwrites = tuple(
        filter(
            lambda overwrite: overwrite.id in (self._guild_id, self.id, *self._role_ids),
            channel.permission_overwrites,
        )
    )

    for everyone_overwrite in filter(lambda overwrite: overwrite.id == self._guild_id, overwrites):
        permissions &= ~everyone_overwrite.deny
        permissions |= everyone_overwrite.allow

    for role_overwrite in filter(lambda overwrite: overwrite.id not in (self._guild_id, self.id), overwrites):
        permissions &= ~role_overwrite.deny
        permissions |= role_overwrite.allow

    for member_overwrite in filter(lambda overwrite: overwrite.id == self.id, overwrites):
        permissions &= ~member_overwrite.deny
        permissions |= member_overwrite.allow

    return permissions

edit_nickname(new_nickname=MISSING, reason=MISSING) async

Change the user's nickname.

Parameters:

Name Type Description Default
new_nickname Absent[str]

The new nickname to apply

MISSING
reason Absent[str]

The reason for this change

MISSING

Note

Leave new_nickname empty to clean user's nickname

Source code in naff/models/discord/user.py
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
async def edit_nickname(self, new_nickname: Absent[str] = MISSING, reason: Absent[str] = MISSING) -> None:
    """
    Change the user's nickname.

    Args:
        new_nickname: The new nickname to apply
        reason: The reason for this change

    !!! note
        Leave `new_nickname` empty to clean user's nickname

    """
    if self.id == self._client.user.id:
        await self._client.http.modify_current_member(self._guild_id, nickname=new_nickname, reason=reason)
    else:
        await self._client.http.modify_guild_member(self._guild_id, self.id, nickname=new_nickname, reason=reason)

add_role(role, reason=MISSING) async

Add a role to this member.

Parameters:

Name Type Description Default
role Union[Snowflake_Type, Role]

The role to add

required
reason Absent[str]

The reason for adding this role

MISSING
Source code in naff/models/discord/user.py
460
461
462
463
464
465
466
467
468
469
470
471
async def add_role(self, role: Union[Snowflake_Type, Role], reason: Absent[str] = MISSING) -> None:
    """
    Add a role to this member.

    Args:
        role: The role to add
        reason: The reason for adding this role

    """
    role = to_snowflake(role)
    await self._client.http.add_guild_member_role(self._guild_id, self.id, role, reason=reason)
    self._role_ids.append(role)

add_roles(roles, reason=MISSING) async

Atomically add multiple roles to this member.

Parameters:

Name Type Description Default
roles Iterable[Union[Snowflake_Type, Role]]

The roles to add

required
reason Absent[str]

The reason for adding the roles

MISSING
Source code in naff/models/discord/user.py
473
474
475
476
477
478
479
480
481
482
483
async def add_roles(self, roles: Iterable[Union[Snowflake_Type, Role]], reason: Absent[str] = MISSING) -> None:
    """
    Atomically add multiple roles to this member.

    Args:
        roles: The roles to add
        reason: The reason for adding the roles

    """
    new_roles = set(self._role_ids) | {to_snowflake(r) for r in roles}
    await self.edit(roles=new_roles, reason=reason)

remove_role(role, reason=MISSING) async

Remove a role from this user.

Parameters:

Name Type Description Default
role Union[Snowflake_Type, Role]

The role to remove

required
reason Absent[str]

The reason for this removal

MISSING
Source code in naff/models/discord/user.py
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
async def remove_role(self, role: Union[Snowflake_Type, Role], reason: Absent[str] = MISSING) -> None:
    """
    Remove a role from this user.

    Args:
        role: The role to remove
        reason: The reason for this removal

    """
    role = to_snowflake(role)
    await self._client.http.remove_guild_member_role(self._guild_id, self.id, role, reason=reason)
    try:
        self._role_ids.remove(role)
    except ValueError:
        pass

remove_roles(roles, reason=MISSING) async

Atomically remove multiple roles from this member.

Parameters:

Name Type Description Default
roles Iterable[Union[Snowflake_Type, Role]]

The roles to remove

required
reason Absent[str]

The reason for removing the roles

MISSING
Source code in naff/models/discord/user.py
501
502
503
504
505
506
507
508
509
510
511
async def remove_roles(self, roles: Iterable[Union[Snowflake_Type, Role]], reason: Absent[str] = MISSING) -> None:
    """
    Atomically remove multiple roles from this member.

    Args:
        roles: The roles to remove
        reason: The reason for removing the roles

    """
    new_roles = set(self._role_ids) - {to_snowflake(r) for r in roles}
    await self.edit(roles=new_roles, reason=reason)

has_role(*roles)

Checks if the user has the given role(s).

Parameters:

Name Type Description Default
*roles Union[Snowflake_Type, Role]

The role(s) to check whether the user has it.

()
Source code in naff/models/discord/user.py
513
514
515
516
517
518
519
520
521
def has_role(self, *roles: Union[Snowflake_Type, Role]) -> bool:
    """
    Checks if the user has the given role(s).

    Args:
        *roles: The role(s) to check whether the user has it.

    """
    return all(to_snowflake(role) in self._role_ids for role in roles)

timeout(communication_disabled_until, reason=MISSING) async

Disable a members communication for a given time.

Parameters:

Name Type Description Default
communication_disabled_until Union[Timestamp, datetime, int, float, str, None]

The time until the user can communicate again

required
reason Absent[str]

The reason for this timeout

MISSING
Source code in naff/models/discord/user.py
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
async def timeout(
    self,
    communication_disabled_until: Union["Timestamp", datetime, int, float, str, None],
    reason: Absent[str] = MISSING,
) -> dict:
    """
    Disable a members communication for a given time.

    Args:
        communication_disabled_until: The time until the user can communicate again
        reason: The reason for this timeout

    """
    if isinstance(communication_disabled_until, (datetime, int, float, str)):
        communication_disabled_until = timestamp_converter(communication_disabled_until)

    self.communication_disabled_until = communication_disabled_until

    return await self._client.http.modify_guild_member(
        self._guild_id,
        self.id,
        communication_disabled_until=communication_disabled_until,
        reason=reason,
    )

move(channel_id) async

Moves the member to a different voice channel.

Parameters:

Name Type Description Default
channel_id Snowflake_Type

The voice channel to move the member to

required
Source code in naff/models/discord/user.py
548
549
550
551
552
553
554
555
556
async def move(self, channel_id: "Snowflake_Type") -> None:
    """
    Moves the member to a different voice channel.

    Args:
        channel_id: The voice channel to move the member to

    """
    await self._client.http.modify_guild_member(self._guild_id, self.id, channel_id=channel_id)

edit(*, nickname=MISSING, roles=MISSING, mute=MISSING, deaf=MISSING, channel_id=MISSING, communication_disabled_until=MISSING, reason=MISSING) async

Modify attrbutes of this guild member.

Parameters:

Name Type Description Default
nickname Absent[str]

Value to set users nickname to

MISSING
roles Absent[Iterable[Snowflake_Type]]

Array of role ids the member is assigned

MISSING
mute Absent[bool]

Whether the user is muted in voice channels. Will throw a 400 if the user is not in a voice channel

MISSING
deaf Absent[bool]

Whether the user is deafened in voice channels

MISSING
channel_id Absent[Snowflake_Type]

id of channel to move user to (if they are connected to voice)

MISSING
communication_disabled_until Absent[Union[Timestamp, None]]

when the user's timeout will expire and the user will be able to communicate in the guild again

MISSING
reason Absent[str]

An optional reason for the audit log

MISSING
Source code in naff/models/discord/user.py
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
async def edit(
    self,
    *,
    nickname: Absent[str] = MISSING,
    roles: Absent[Iterable["Snowflake_Type"]] = MISSING,
    mute: Absent[bool] = MISSING,
    deaf: Absent[bool] = MISSING,
    channel_id: Absent["Snowflake_Type"] = MISSING,
    communication_disabled_until: Absent[Union["Timestamp", None]] = MISSING,
    reason: Absent[str] = MISSING,
) -> None:
    """
    Modify attrbutes of this guild member.

    Args:
        nickname: Value to set users nickname to
        roles: Array of role ids the member is assigned
        mute: Whether the user is muted in voice channels. Will throw a 400 if the user is not in a voice channel
        deaf: Whether the user is deafened in voice channels
        channel_id: id of channel to move user to (if they are connected to voice)
        communication_disabled_until: 	when the user's timeout will expire and the user will be able to communicate in the guild again
        reason: An optional reason for the audit log
    """
    await self._client.http.modify_guild_member(
        self._guild_id,
        self.id,
        nickname=nickname,
        roles=roles,
        mute=mute,
        deaf=deaf,
        channel_id=channel_id,
        communication_disabled_until=communication_disabled_until,
        reason=reason,
    )

kick(reason=MISSING) async

Remove a member from the guild.

Parameters:

Name Type Description Default
reason Absent[str]

The reason for this removal

MISSING
Source code in naff/models/discord/user.py
593
594
595
596
597
598
599
600
601
async def kick(self, reason: Absent[str] = MISSING) -> None:
    """
    Remove a member from the guild.

    Args:
        reason: The reason for this removal

    """
    await self._client.http.remove_guild_member(self._guild_id, self.id, reason=reason)

ban(delete_message_days=MISSING, delete_message_seconds=0, reason=MISSING) async

Ban a member from the guild.

Parameters:

Name Type Description Default
delete_message_days int

(deprecated) The number of days of messages to delete

MISSING
delete_message_seconds int

The number of seconds of messages to delete

0
reason Absent[str]

The reason for this ban

MISSING
Source code in naff/models/discord/user.py
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
async def ban(
    self, delete_message_days: Absent[int] = MISSING, delete_message_seconds: int = 0, reason: Absent[str] = MISSING
) -> None:
    """
    Ban a member from the guild.

    Args:
        delete_message_days: (deprecated) The number of days of messages to delete
        delete_message_seconds: The number of seconds of messages to delete
        reason: The reason for this ban

    """
    if delete_message_days is not MISSING:
        warn("delete_message_days  is deprecated and will be removed in a future update", DeprecationWarning)
        delete_message_seconds = delete_message_days * 3600
    await self._client.http.create_guild_ban(self._guild_id, self.id, delete_message_seconds, reason=reason)

VoiceState

Bases: ClientObject

Source code in naff/models/discord/voice_state.py
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
@define()
class VoiceState(ClientObject):
    user_id: "Snowflake_Type" = field(default=MISSING, converter=to_snowflake)
    """the user id this voice state is for"""
    session_id: str = field(default=MISSING)
    """the session id for this voice state"""
    deaf: bool = field(default=False)
    """whether this user is deafened by the server"""
    mute: bool = field(default=False)
    """whether this user is muted by the server"""
    self_deaf: bool = field(default=False)
    """whether this user is locally deafened"""
    self_mute: bool = field(default=False)
    """whether this user is locally muted"""
    self_stream: Optional[bool] = field(default=False)
    """whether this user is streaming using "Go Live\""""
    self_video: bool = field(default=False)
    """whether this user's camera is enabled"""
    suppress: bool = field(default=False)
    """whether this user is muted by the current user"""
    request_to_speak_timestamp: Optional[Timestamp] = field(default=None, converter=optional_c(timestamp_converter))
    """the time at which the user requested to speak"""

    # internal for props
    _guild_id: Optional["Snowflake_Type"] = field(default=None, converter=to_snowflake)
    _channel_id: "Snowflake_Type" = field(converter=to_snowflake)
    _member_id: Optional["Snowflake_Type"] = field(default=None, converter=to_snowflake)

    @property
    def guild(self) -> "Guild":
        """The guild this voice state is for."""
        return self._client.cache.get_guild(self._guild_id) if self._guild_id else None

    @property
    def channel(self) -> "TYPE_VOICE_CHANNEL":
        """The channel the user is connected to."""
        channel: "TYPE_VOICE_CHANNEL" = self._client.cache.get_channel(self._channel_id)

        # make sure the member is showing up as a part of the channel
        # this is relevant for VoiceStateUpdate.before
        # noinspection PyProtectedMember
        if self._member_id not in channel._voice_member_ids:
            # the list of voice members need to be deepcopied, otherwise the cached obj will be updated
            # noinspection PyProtectedMember
            voice_member_ids = copy.deepcopy(channel._voice_member_ids)

            # create a copy of the obj
            channel = copy.copy(channel)
            channel._voice_member_ids = voice_member_ids

            # add the member to that list
            # noinspection PyProtectedMember
            channel._voice_member_ids.append(self._member_id)

        return channel

    @property
    def member(self) -> "Member":
        """The member this voice state is for."""
        return self._client.cache.get_member(self._guild_id, self._member_id) if self._guild_id else None

    @classmethod
    def _process_dict(cls, data: Dict[str, Any], client: "Client") -> Dict[str, Any]:
        if member := data.pop("member", None):
            member = client.cache.place_member_data(data["guild_id"], member)
            data["member_id"] = member.id
        else:
            data["member_id"] = data["user_id"]
        return data

user_id: Snowflake_Type = field(default=MISSING, converter=to_snowflake) class-attribute

the user id this voice state is for

session_id: str = field(default=MISSING) class-attribute

the session id for this voice state

deaf: bool = field(default=False) class-attribute

whether this user is deafened by the server

mute: bool = field(default=False) class-attribute

whether this user is muted by the server

self_deaf: bool = field(default=False) class-attribute

whether this user is locally deafened

self_mute: bool = field(default=False) class-attribute

whether this user is locally muted

self_stream: Optional[bool] = field(default=False) class-attribute

whether this user is streaming using "Go Live"

self_video: bool = field(default=False) class-attribute

whether this user's camera is enabled

suppress: bool = field(default=False) class-attribute

whether this user is muted by the current user

request_to_speak_timestamp: Optional[Timestamp] = field(default=None, converter=optional_c(timestamp_converter)) class-attribute

the time at which the user requested to speak

guild() property

The guild this voice state is for.

Source code in naff/models/discord/voice_state.py
50
51
52
53
@property
def guild(self) -> "Guild":
    """The guild this voice state is for."""
    return self._client.cache.get_guild(self._guild_id) if self._guild_id else None

channel() property

The channel the user is connected to.

Source code in naff/models/discord/voice_state.py
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
@property
def channel(self) -> "TYPE_VOICE_CHANNEL":
    """The channel the user is connected to."""
    channel: "TYPE_VOICE_CHANNEL" = self._client.cache.get_channel(self._channel_id)

    # make sure the member is showing up as a part of the channel
    # this is relevant for VoiceStateUpdate.before
    # noinspection PyProtectedMember
    if self._member_id not in channel._voice_member_ids:
        # the list of voice members need to be deepcopied, otherwise the cached obj will be updated
        # noinspection PyProtectedMember
        voice_member_ids = copy.deepcopy(channel._voice_member_ids)

        # create a copy of the obj
        channel = copy.copy(channel)
        channel._voice_member_ids = voice_member_ids

        # add the member to that list
        # noinspection PyProtectedMember
        channel._voice_member_ids.append(self._member_id)

    return channel

member() property

The member this voice state is for.

Source code in naff/models/discord/voice_state.py
78
79
80
81
@property
def member(self) -> "Member":
    """The member this voice state is for."""
    return self._client.cache.get_member(self._guild_id, self._member_id) if self._guild_id else None

VoiceRegion

Bases: DictSerializationMixin

A voice region.

Source code in naff/models/discord/voice_state.py
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
@define()
class VoiceRegion(DictSerializationMixin):
    """A voice region."""

    id: str = field(repr=True)
    """unique ID for the region"""
    name: str = field(repr=True)
    """name of the region"""
    vip: bool = field(default=False, repr=True)
    """whether this is a VIP-only voice region"""
    optimal: bool = field(default=False)
    """true for a single server that is closest to the current user's client"""
    deprecated: bool = field(default=False)
    """whether this is a deprecated voice region (avoid switching to these)"""
    custom: bool = field(default=False)
    """whether this is a custom voice region (used for events/etc)"""

    def __str__(self) -> str:
        return self.name

id: str = field(repr=True) class-attribute

unique ID for the region

name: str = field(repr=True) class-attribute

name of the region

vip: bool = field(default=False, repr=True) class-attribute

whether this is a VIP-only voice region

optimal: bool = field(default=False) class-attribute

true for a single server that is closest to the current user's client

deprecated: bool = field(default=False) class-attribute

whether this is a deprecated voice region (avoid switching to these)

custom: bool = field(default=False) class-attribute

whether this is a custom voice region (used for events/etc)


Guild

GuildBan

Source code in naff/models/discord/guild.py
62
63
64
65
66
67
@define()
class GuildBan:
    reason: Optional[str]
    """The reason for the ban"""
    user: "models.User"
    """The banned user"""

reason: Optional[str] class-attribute

The reason for the ban

user: models.User class-attribute

The banned user

BaseGuild

Bases: DiscordObject

Source code in naff/models/discord/guild.py
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
@define()
class BaseGuild(DiscordObject):
    name: str = field(repr=True)
    """Name of guild. (2-100 characters, excluding trailing and leading whitespace)"""
    description: Optional[str] = field(repr=True, default=None)
    """The description for the guild, if the guild is discoverable"""

    icon: Optional["models.Asset"] = field(default=None)
    """Icon image asset"""
    splash: Optional["models.Asset"] = field(default=None)
    """Splash image asset"""
    discovery_splash: Optional["models.Asset"] = field(default=None)
    """Discovery splash image. Only present for guilds with the "DISCOVERABLE" feature."""
    features: List[str] = field(factory=list)
    """The features of this guild"""

    @classmethod
    def _process_dict(cls, data: Dict[str, Any], client: "Client") -> Dict[str, Any]:
        if icon_hash := data.pop("icon", None):
            data["icon"] = models.Asset.from_path_hash(client, f"icons/{data['id']}/{{}}", icon_hash)
        if splash_hash := data.pop("splash", None):
            data["splash"] = models.Asset.from_path_hash(client, f"splashes/{data['id']}/{{}}", splash_hash)
        if disco_splash := data.pop("discovery_splash", None):
            data["discovery_splash"] = models.Asset.from_path_hash(
                client, f"discovery-splashes/{data['id']}/{{}}", disco_splash
            )
        return data

name: str = field(repr=True) class-attribute

Name of guild. (2-100 characters, excluding trailing and leading whitespace)

description: Optional[str] = field(repr=True, default=None) class-attribute

The description for the guild, if the guild is discoverable

icon: Optional[models.Asset] = field(default=None) class-attribute

Icon image asset

splash: Optional[models.Asset] = field(default=None) class-attribute

Splash image asset

discovery_splash: Optional[models.Asset] = field(default=None) class-attribute

Discovery splash image. Only present for guilds with the "DISCOVERABLE" feature.

features: List[str] = field(factory=list) class-attribute

The features of this guild

GuildPreview

Bases: BaseGuild

A partial guild object.

Source code in naff/models/discord/guild.py
107
108
109
110
111
112
113
114
115
116
117
118
119
120
@define()
class GuildPreview(BaseGuild):
    """A partial guild object."""

    emoji: list["models.PartialEmoji"] = field(factory=list)
    """A list of custom emoji from this guild"""
    approximate_member_count: int = field(default=0)
    """Approximate number of members in this guild"""
    approximate_presence_count: int = field(default=0)
    """Approximate number of online members in this guild"""

    @classmethod
    def _process_dict(cls, data: Dict[str, Any], client: "Client") -> Dict[str, Any]:
        return super()._process_dict(data, client)

emoji: list[models.PartialEmoji] = field(factory=list) class-attribute

A list of custom emoji from this guild

approximate_member_count: int = field(default=0) class-attribute

Approximate number of members in this guild

approximate_presence_count: int = field(default=0) class-attribute

Approximate number of online members in this guild

Guild

Bases: BaseGuild

Guilds in Discord represent an isolated collection of users and channels, and are often referred to as "servers" in the UI.

Source code in naff/models/discord/guild.py
 143
 144
 145
 146
 147
 148
 149
 150
 151
 152
 153
 154
 155
 156
 157
 158
 159
 160
 161
 162
 163
 164
 165
 166
 167
 168
 169
 170
 171
 172
 173
 174
 175
 176
 177
 178
 179
 180
 181
 182
 183
 184
 185
 186
 187
 188
 189
 190
 191
 192
 193
 194
 195
 196
 197
 198
 199
 200
 201
 202
 203
 204
 205
 206
 207
 208
 209
 210
 211
 212
 213
 214
 215
 216
 217
 218
 219
 220
 221
 222
 223
 224
 225
 226
 227
 228
 229
 230
 231
 232
 233
 234
 235
 236
 237
 238
 239
 240
 241
 242
 243
 244
 245
 246
 247
 248
 249
 250
 251
 252
 253
 254
 255
 256
 257
 258
 259
 260
 261
 262
 263
 264
 265
 266
 267
 268
 269
 270
 271
 272
 273
 274
 275
 276
 277
 278
 279
 280
 281
 282
 283
 284
 285
 286
 287
 288
 289
 290
 291
 292
 293
 294
 295
 296
 297
 298
 299
 300
 301
 302
 303
 304
 305
 306
 307
 308
 309
 310
 311
 312
 313
 314
 315
 316
 317
 318
 319
 320
 321
 322
 323
 324
 325
 326
 327
 328
 329
 330
 331
 332
 333
 334
 335
 336
 337
 338
 339
 340
 341
 342
 343
 344
 345
 346
 347
 348
 349
 350
 351
 352
 353
 354
 355
 356
 357
 358
 359
 360
 361
 362
 363
 364
 365
 366
 367
 368
 369
 370
 371
 372
 373
 374
 375
 376
 377
 378
 379
 380
 381
 382
 383
 384
 385
 386
 387
 388
 389
 390
 391
 392
 393
 394
 395
 396
 397
 398
 399
 400
 401
 402
 403
 404
 405
 406
 407
 408
 409
 410
 411
 412
 413
 414
 415
 416
 417
 418
 419
 420
 421
 422
 423
 424
 425
 426
 427
 428
 429
 430
 431
 432
 433
 434
 435
 436
 437
 438
 439
 440
 441
 442
 443
 444
 445
 446
 447
 448
 449
 450
 451
 452
 453
 454
 455
 456
 457
 458
 459
 460
 461
 462
 463
 464
 465
 466
 467
 468
 469
 470
 471
 472
 473
 474
 475
 476
 477
 478
 479
 480
 481
 482
 483
 484
 485
 486
 487
 488
 489
 490
 491
 492
 493
 494
 495
 496
 497
 498
 499
 500
 501
 502
 503
 504
 505
 506
 507
 508
 509
 510
 511
 512
 513
 514
 515
 516
 517
 518
 519
 520
 521
 522
 523
 524
 525
 526
 527
 528
 529
 530
 531
 532
 533
 534
 535
 536
 537
 538
 539
 540
 541
 542
 543
 544
 545
 546
 547
 548
 549
 550
 551
 552
 553
 554
 555
 556
 557
 558
 559
 560
 561
 562
 563
 564
 565
 566
 567
 568
 569
 570
 571
 572
 573
 574
 575
 576
 577
 578
 579
 580
 581
 582
 583
 584
 585
 586
 587
 588
 589
 590
 591
 592
 593
 594
 595
 596
 597
 598
 599
 600
 601
 602
 603
 604
 605
 606
 607
 608
 609
 610
 611
 612
 613
 614
 615
 616
 617
 618
 619
 620
 621
 622
 623
 624
 625
 626
 627
 628
 629
 630
 631
 632
 633
 634
 635
 636
 637
 638
 639
 640
 641
 642
 643
 644
 645
 646
 647
 648
 649
 650
 651
 652
 653
 654
 655
 656
 657
 658
 659
 660
 661
 662
 663
 664
 665
 666
 667
 668
 669
 670
 671
 672
 673
 674
 675
 676
 677
 678
 679
 680
 681
 682
 683
 684
 685
 686
 687
 688
 689
 690
 691
 692
 693
 694
 695
 696
 697
 698
 699
 700
 701
 702
 703
 704
 705
 706
 707
 708
 709
 710
 711
 712
 713
 714
 715
 716
 717
 718
 719
 720
 721
 722
 723
 724
 725
 726
 727
 728
 729
 730
 731
 732
 733
 734
 735
 736
 737
 738
 739
 740
 741
 742
 743
 744
 745
 746
 747
 748
 749
 750
 751
 752
 753
 754
 755
 756
 757
 758
 759
 760
 761
 762
 763
 764
 765
 766
 767
 768
 769
 770
 771
 772
 773
 774
 775
 776
 777
 778
 779
 780
 781
 782
 783
 784
 785
 786
 787
 788
 789
 790
 791
 792
 793
 794
 795
 796
 797
 798
 799
 800
 801
 802
 803
 804
 805
 806
 807
 808
 809
 810
 811
 812
 813
 814
 815
 816
 817
 818
 819
 820
 821
 822
 823
 824
 825
 826
 827
 828
 829
 830
 831
 832
 833
 834
 835
 836
 837
 838
 839
 840
 841
 842
 843
 844
 845
 846
 847
 848
 849
 850
 851
 852
 853
 854
 855
 856
 857
 858
 859
 860
 861
 862
 863
 864
 865
 866
 867
 868
 869
 870
 871
 872
 873
 874
 875
 876
 877
 878
 879
 880
 881
 882
 883
 884
 885
 886
 887
 888
 889
 890
 891
 892
 893
 894
 895
 896
 897
 898
 899
 900
 901
 902
 903
 904
 905
 906
 907
 908
 909
 910
 911
 912
 913
 914
 915
 916
 917
 918
 919
 920
 921
 922
 923
 924
 925
 926
 927
 928
 929
 930
 931
 932
 933
 934
 935
 936
 937
 938
 939
 940
 941
 942
 943
 944
 945
 946
 947
 948
 949
 950
 951
 952
 953
 954
 955
 956
 957
 958
 959
 960
 961
 962
 963
 964
 965
 966
 967
 968
 969
 970
 971
 972
 973
 974
 975
 976
 977
 978
 979
 980
 981
 982
 983
 984
 985
 986
 987
 988
 989
 990
 991
 992
 993
 994
 995
 996
 997
 998
 999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
1631
1632
1633
1634
1635
1636
1637
1638
1639
1640
1641
1642
1643
1644
1645
1646
1647
1648
1649
1650
1651
1652
1653
1654
1655
1656
1657
1658
1659
1660
1661
1662
1663
1664
1665
1666
1667
1668
1669
1670
1671
1672
1673
1674
1675
1676
1677
1678
1679
1680
1681
1682
1683
1684
1685
1686
1687
1688
1689
1690
1691
1692
1693
1694
1695
1696
1697
1698
1699
1700
1701
1702
1703
1704
1705
1706
1707
1708
1709
1710
1711
1712
1713
1714
1715
1716
1717
1718
1719
1720
1721
1722
1723
1724
1725
1726
1727
1728
1729
1730
1731
1732
1733
1734
1735
1736
1737
1738
1739
1740
1741
1742
1743
1744
1745
1746
1747
1748
1749
1750
1751
1752
1753
1754
1755
1756
1757
1758
1759
1760
1761
1762
1763
1764
1765
1766
1767
1768
1769
1770
1771
1772
1773
1774
1775
1776
1777
1778
1779
1780
1781
1782
1783
1784
1785
1786
1787
1788
1789
1790
1791
1792
1793
1794
1795
1796
1797
1798
1799
1800
1801
1802
1803
1804
1805
1806
1807
1808
1809
1810
1811
1812
1813
1814
1815
1816
1817
1818
1819
1820
1821
1822
1823
1824
1825
1826
1827
1828
1829
1830
1831
1832
1833
1834
1835
1836
1837
1838
1839
1840
1841
1842
1843
1844
1845
1846
1847
1848
1849
1850
1851
1852
1853
1854
1855
1856
1857
1858
1859
1860
1861
1862
1863
1864
1865
1866
1867
1868
1869
1870
1871
1872
1873
1874
1875
1876
1877
1878
1879
1880
1881
1882
1883
1884
1885
1886
1887
1888
1889
1890
1891
1892
1893
1894
1895
1896
1897
1898
1899
1900
1901
1902
1903
1904
1905
1906
1907
1908
1909
1910
1911
1912
1913
1914
1915
1916
1917
1918
1919
1920
1921
1922
1923
1924
1925
1926
1927
1928
1929
1930
1931
1932
1933
1934
1935
1936
1937
1938
1939
1940
1941
1942
1943
1944
1945
1946
1947
1948
1949
1950
1951
1952
1953
1954
1955
1956
1957
1958
1959
1960
1961
1962
1963
1964
1965
1966
1967
1968
1969
1970
1971
1972
@define()
class Guild(BaseGuild):
    """Guilds in Discord represent an isolated collection of users and channels, and are often referred to as "servers" in the UI."""

    unavailable: bool = field(default=False)
    """True if this guild is unavailable due to an outage."""
    # owner: bool = field(default=False)  # we get this from api but it's kinda useless to store
    afk_channel_id: Optional[Snowflake_Type] = field(default=None)
    """The channel id for afk."""
    afk_timeout: Optional[int] = field(default=None)
    """afk timeout in seconds."""
    widget_enabled: bool = field(default=False)
    """True if the server widget is enabled."""
    widget_channel_id: Optional[Snowflake_Type] = field(default=None)
    """The channel id that the widget will generate an invite to, or None if set to no invite."""
    verification_level: Union[VerificationLevels, int] = field(default=VerificationLevels.NONE)
    """The verification level required for the guild."""
    default_message_notifications: Union[DefaultNotificationLevels, int] = field(
        default=DefaultNotificationLevels.ALL_MESSAGES
    )
    """The default message notifications level."""
    explicit_content_filter: Union[ExplicitContentFilterLevels, int] = field(
        default=ExplicitContentFilterLevels.DISABLED
    )
    """The explicit content filter level."""
    mfa_level: Union[MFALevels, int] = field(default=MFALevels.NONE)
    """The required MFA (Multi Factor Authentication) level for the guild."""
    system_channel_id: Optional[Snowflake_Type] = field(default=None)
    """The id of the channel where guild notices such as welcome messages and boost events are posted."""
    system_channel_flags: SystemChannelFlags = field(default=SystemChannelFlags.NONE, converter=SystemChannelFlags)
    """The system channel flags."""
    rules_channel_id: Optional[Snowflake_Type] = field(default=None)
    """The id of the channel where Community guilds can display rules and/or guidelines."""
    joined_at: str = field(default=None, converter=optional(timestamp_converter))
    """When this guild was joined at."""
    large: bool = field(default=False)
    """True if this is considered a large guild."""
    member_count: int = field(default=0)
    """The total number of members in this guild."""
    presences: List[dict] = field(factory=list)
    """The presences of the members in the guild, will only include non-offline members if the size is greater than large threshold."""
    max_presences: Optional[int] = field(default=None)
    """The maximum number of presences for the guild. (None is always returned, apart from the largest of guilds)"""
    max_members: Optional[int] = field(default=None)
    """The maximum number of members for the guild."""
    vanity_url_code: Optional[str] = field(default=None)
    """The vanity url code for the guild."""
    banner: Optional[str] = field(default=None)
    """Hash for banner image."""
    premium_tier: Optional[str] = field(default=None)
    """The premium tier level. (Server Boost level)"""
    premium_subscription_count: int = field(default=0)
    """The number of boosts this guild currently has."""
    preferred_locale: str = field()
    """The preferred locale of a Community guild. Used in server discovery and notices from Discord. Defaults to \"en-US\""""
    public_updates_channel_id: Optional[Snowflake_Type] = field(default=None)
    """The id of the channel where admins and moderators of Community guilds receive notices from Discord."""
    max_video_channel_users: int = field(default=0)
    """The maximum amount of users in a video channel."""
    welcome_screen: Optional["GuildWelcome"] = field(default=None)
    """The welcome screen of a Community guild, shown to new members, returned in an Invite's guild object."""
    nsfw_level: Union[NSFWLevels, int] = field(default=NSFWLevels.DEFAULT)
    """The guild NSFW level."""
    stage_instances: List[dict] = field(factory=list)  # TODO stage instance objects
    """Stage instances in the guild."""
    chunked = field(factory=asyncio.Event, metadata=no_export_meta)
    """An event that is fired when this guild has been chunked"""

    _owner_id: Snowflake_Type = field(converter=to_snowflake)
    _channel_ids: Set[Snowflake_Type] = field(factory=set)
    _thread_ids: Set[Snowflake_Type] = field(factory=set)
    _member_ids: Set[Snowflake_Type] = field(factory=set)
    _role_ids: Set[Snowflake_Type] = field(factory=set)
    _chunk_cache: list = field(factory=list)
    _channel_gui_positions: Dict[Snowflake_Type, int] = field(factory=dict)

    @classmethod
    def _process_dict(cls, data: Dict[str, Any], client: "Client") -> Dict[str, Any]:
        # todo: find a away to prevent this loop from blocking the event loop
        data = super()._process_dict(data, client)
        guild_id = data["id"]

        channels_data = data.pop("channels", [])
        for c in channels_data:
            c["guild_id"] = guild_id
        data["channel_ids"] = {client.cache.place_channel_data(channel_data).id for channel_data in channels_data}

        threads_data = data.pop("threads", [])
        data["thread_ids"] = {client.cache.place_channel_data(thread_data).id for thread_data in threads_data}

        members_data = data.pop("members", [])
        data["member_ids"] = {client.cache.place_member_data(guild_id, member_data).id for member_data in members_data}

        roles_data = data.pop("roles", [])
        data["role_ids"] = set(client.cache.place_role_data(guild_id, roles_data).keys())

        if welcome_screen := data.get("welcome_screen"):
            data["welcome_screen"] = GuildWelcome.from_dict(welcome_screen, client)

        if voice_states := data.get("voice_states"):
            [
                asyncio.create_task(client.cache.place_voice_state_data(state | {"guild_id": guild_id}))
                for state in voice_states
            ]
        return data

    @classmethod
    async def create(
        cls,
        name: str,
        client: "Client",
        *,
        icon: Absent[Optional[UPLOADABLE_TYPE]] = MISSING,
        verification_level: Absent["VerificationLevels"] = MISSING,
        default_message_notifications: Absent["DefaultNotificationLevels"] = MISSING,
        explicit_content_filter: Absent["ExplicitContentFilterLevels"] = MISSING,
        roles: Absent[list[dict]] = MISSING,
        channels: Absent[list[dict]] = MISSING,
        afk_channel_id: Absent["Snowflake_Type"] = MISSING,
        afk_timeout: Absent[int] = MISSING,
        system_channel_id: Absent["Snowflake_Type"] = MISSING,
        system_channel_flags: Absent["SystemChannelFlags"] = MISSING,
    ) -> "Guild":
        """
        Create a guild.

        !!! note
            This method will only work for bots in less than 10 guilds.

        ??? note "Param notes"
            Roles:
                - When using the `roles` parameter, the first member of the array is used to change properties of the guild's `@everyone` role. If you are trying to bootstrap a guild with additional roles, keep this in mind.
                - When using the `roles` parameter, the required id field within each role object is an integer placeholder, and will be replaced by the API upon consumption. Its purpose is to allow you to overwrite a role's permissions in a channel when also passing in channels with the channels array.

            Channels:
                - When using the `channels` parameter, the position field is ignored, and none of the default channels are created.
                - When using the `channels` parameter, the id field within each channel object may be set to an integer placeholder, and will be replaced by the API upon consumption. Its purpose is to allow you to create `GUILD_CATEGORY` channels by setting the `parent_id` field on any children to the category's id field. Category channels must be listed before any children.

        Args:
            name: name of the guild (2-100 characters)
            client: The NAFF client
            icon: An icon for the guild
            verification_level: The guild's verification level
            default_message_notifications: The default message notification level
            explicit_content_filter: The guild's explicit content filter level
            roles: An array of partial role dictionaries
            channels: An array of partial channel dictionaries
            afk_channel_id: id for afk channel
            afk_timeout: afk timeout in seconds
            system_channel_id: the id of the channel where guild notices should go
            system_channel_flags: flags for the system channel

        Returns:
            The created guild object

        """
        data = await client.http.create_guild(
            name=name,
            icon=to_image_data(icon) if icon else MISSING,
            verification_level=verification_level,
            default_message_notifications=default_message_notifications,
            explicit_content_filter=explicit_content_filter,
            roles=roles,
            channels=channels,
            afk_channel_id=afk_channel_id,
            afk_timeout=afk_timeout,
            system_channel_id=system_channel_id,
            system_channel_flags=int(system_channel_flags) if system_channel_flags else MISSING,
        )
        return client.cache.place_guild_data(data)

    @property
    def channels(self) -> List["models.TYPE_GUILD_CHANNEL"]:
        """Returns a list of channels associated with this guild."""
        return [self._client.cache.get_channel(c_id) for c_id in self._channel_ids]

    @property
    def threads(self) -> List["models.TYPE_THREAD_CHANNEL"]:
        """Returns a list of threads associated with this guild."""
        return [self._client.cache.get_channel(t_id) for t_id in self._thread_ids]

    @property
    def members(self) -> List["models.Member"]:
        """Returns a list of all members within this guild."""
        return [self._client.cache.get_member(self.id, m_id) for m_id in self._member_ids]

    @property
    def premium_subscribers(self) -> List["models.Member"]:
        """Returns a list of all premium subscribers"""
        return [member for member in self.members if member.premium]

    @property
    def bots(self) -> List["models.Member"]:
        """Returns a list of all bots within this guild"""
        return [member for member in self.members if member.bot]

    @property
    def humans(self) -> List["models.Member"]:
        """Returns a list of all humans within this guild"""
        return [member for member in self.members if not member.bot]

    @property
    def roles(self) -> List["models.Role"]:
        """Returns a list of roles associated with this guild."""
        return [self._client.cache.get_role(r_id) for r_id in self._role_ids]

    @property
    def me(self) -> "models.Member":
        """Returns this bots member object within this guild."""
        return self._client.cache.get_member(self.id, self._client.user.id)

    @property
    def system_channel(self) -> Optional["models.GuildText"]:
        """Returns the channel this guild uses for system messages."""
        return self._client.cache.get_channel(self.system_channel_id)

    @property
    def rules_channel(self) -> Optional["models.GuildText"]:
        """Returns the channel declared as a rules channel."""
        return self._client.cache.get_channel(self.rules_channel_id)

    @property
    def public_updates_channel(self) -> Optional["models.GuildText"]:
        """Returns the channel where server staff receive notices from Discord."""
        return self._client.cache.get_channel(self.public_updates_channel_id)

    @property
    def emoji_limit(self) -> int:
        """The maximum number of emoji this guild can have."""
        base = 200 if "MORE_EMOJI" in self.features else 50
        return max(base, PREMIUM_GUILD_LIMITS[self.premium_tier]["emoji"])

    @property
    def sticker_limit(self) -> int:
        """The maximum number of stickers this guild can have."""
        base = 60 if "MORE_STICKERS" in self.features else 0
        return max(base, PREMIUM_GUILD_LIMITS[self.premium_tier]["stickers"])

    @property
    def bitrate_limit(self) -> int:
        """The maximum bitrate for this guild."""
        base = 128000 if "VIP_REGIONS" in self.features else 96000
        return max(base, PREMIUM_GUILD_LIMITS[self.premium_tier]["bitrate"])

    @property
    def filesize_limit(self) -> int:
        """The maximum filesize that may be uploaded within this guild."""
        return PREMIUM_GUILD_LIMITS[self.premium_tier]["filesize"]

    @property
    def default_role(self) -> "models.Role":
        """The `@everyone` role in this guild."""
        return self._client.cache.get_role(self.id)  # type: ignore

    @property
    def premium_subscriber_role(self) -> Optional["models.Role"]:
        """The role given to boosters of this server, if set."""
        for role in self.roles:
            if role.premium_subscriber:
                return role
        return None

    @property
    def my_role(self) -> Optional["models.Role"]:
        """The role associated with this client, if set."""
        m_r_id = self._client.user.id
        for role in self.roles:
            if role._bot_id == m_r_id:
                return role
        return None

    @property
    def permissions(self) -> Permissions:
        """Alias for me.guild_permissions"""
        return self.me.guild_permissions

    @property
    def voice_state(self) -> Optional["models.VoiceState"]:
        """Get the bot's voice state for the guild."""
        return self._client.cache.get_bot_voice_state(self.id)

    @property
    def voice_states(self) -> List["models.VoiceState"]:
        """Get a list of the active voice states in this guild."""
        # this is *very* ick, but we cache by user_id, so we have to do it this way,
        # alternative would be maintaining a lookup table in this guild object, which is inherently unreliable
        # noinspection PyProtectedMember
        return [v_state for v_state in self._client.cache.voice_state_cache.values() if v_state._guild_id == self.id]

    async def fetch_member(self, member_id: Snowflake_Type) -> Optional["models.Member"]:
        """
        Return the Member with the given discord ID, fetching from the API if necessary.

        Args:
            member_id: The ID of the member.

        Returns:
            The member object fetched. If the member is not in this guild, returns None.

        """
        try:
            return await self._client.cache.fetch_member(self.id, member_id)
        except NotFound:
            return None

    def get_member(self, member_id: Snowflake_Type) -> Optional["models.Member"]:
        """
        Return the Member with the given discord ID.

        Args:
            member_id: The ID of the member

        Returns:
            Member object or None

        """
        return self._client.cache.get_member(self.id, member_id)

    async def fetch_owner(self) -> "models.Member":
        """
        Return the Guild owner, fetching from the API if necessary.

        Returns:
            Member object or None

        """
        return await self._client.cache.fetch_member(self.id, self._owner_id)

    def get_owner(self) -> "models.Member":
        """
        Return the Guild owner

        Returns:
            Member object or None

        """
        return self._client.cache.get_member(self.id, self._owner_id)

    async def fetch_channels(self) -> List["models.TYPE_VOICE_CHANNEL"]:
        """
        Fetch this guild's channels.

        Returns:
            A list of channels in this guild

        """
        data = await self._client.http.get_guild_channels(self.id)
        return [self._client.cache.place_channel_data(channel_data) for channel_data in data]

    def is_owner(self, user: Snowflake_Type) -> bool:
        """
        Whether the user is owner of the guild.

        Args:
            user: The user to check

        Returns:
            True if the user is the owner of the guild, False otherwise.

        !!! note
            the `user` argument can be any type that meets `Snowflake_Type`

        """
        return self._owner_id == to_snowflake(user)

    async def edit_nickname(self, new_nickname: Absent[str] = MISSING, reason: Absent[str] = MISSING) -> None:
        """
        Alias for me.edit_nickname

        Args:
            new_nickname: The new nickname to apply
            reason: The reason for this change

        !!! note
            Leave `new_nickname` empty to clean user's nickname

        """
        await self.me.edit_nickname(new_nickname, reason=reason)

    async def http_chunk(self) -> None:
        """Populates all members of this guild using the REST API."""
        start_time = time.perf_counter()

        iterator = MemberIterator(self)
        async for member in iterator:
            self._client.cache.place_member_data(self.id, member)

        self.chunked.set()
        logger.info(
            f"Cached {iterator.total_retrieved} members for {self.id} in {time.perf_counter() - start_time:.2f} seconds"
        )

    async def gateway_chunk(self, wait=True, presences=True) -> None:
        """
        Trigger a gateway `get_members` event, populating this object with members.

        Args:
            wait: Wait for chunking to be completed before continuing
            presences: Do you need presence data for members?
        """
        ws = self._client.get_guild_websocket(self.id)
        await ws.request_member_chunks(self.id, limit=0, presences=presences)
        if wait:
            await self.chunked.wait()

    async def chunk(self) -> None:
        """Populates all members of this guild using the REST API."""
        await self.http_chunk()

    async def chunk_guild(self, wait=True, presences=True) -> None:
        """
        Trigger a gateway `get_members` event, populating this object with members.

        !!! warning "Depreciation Warning"
            Gateway chunking is deprecated and replaced by http chunking. Use `guild.gateway_chunk` if you need gateway chunking.

        Args:
            wait: Wait for chunking to be completed before continuing
            presences: Do you need presence data for members?

        """
        warn(
            "Gateway chunking is deprecated and replaced by http chunking. Use `guild.gateway_chunk` if you need gateway chunking.",
            DeprecationWarning,
            stacklevel=2,
        )
        await self.gateway_chunk(wait=wait, presences=presences)

    async def process_member_chunk(self, chunk: dict) -> None:
        """
        Receive and either cache or process the chunks of members from gateway.

        Args:
            chunk: A member chunk from discord

        """
        if self.chunked.is_set():
            self.chunked.clear()

        if presences := chunk.get("presences"):
            # combine the presence dict into the members dict
            for presence in presences:
                u_id = presence["user"]["id"]
                # find the user this presence is for
                member_index = next(
                    (index for (index, d) in enumerate(chunk.get("members")) if d["user"]["id"] == u_id), None
                )
                del presence["user"]
                chunk["members"][member_index]["user"] = chunk["members"][member_index]["user"] | presence

        if not self._chunk_cache:
            self._chunk_cache: List = chunk.get("members")
        else:
            self._chunk_cache = self._chunk_cache + chunk.get("members")

        if chunk.get("chunk_index") != chunk.get("chunk_count") - 1:
            return logger.debug(f"Cached chunk of {len(chunk.get('members'))} members for {self.id}")
        else:
            members = self._chunk_cache
            logger.info(f"Processing {len(members)} members for {self.id}")

            s = time.monotonic()
            start_time = time.perf_counter()

            for member in members:
                self._client.cache.place_member_data(self.id, member)
                if (time.monotonic() - s) > 0.05:
                    # look, i get this *could* be a thread, but because it needs to modify data in the main thread,
                    # it is still blocking. So by periodically yielding to the event loop, we can avoid blocking, and still
                    # process this data properly
                    await asyncio.sleep(0)
                    s = time.monotonic()

            total_time = time.perf_counter() - start_time
            self.chunk_cache = []
            logger.info(f"Cached members for {self.id} in {total_time:.2f} seconds")
            self.chunked.set()

    async def fetch_audit_log(
        self,
        user_id: Optional["Snowflake_Type"] = MISSING,
        action_type: Optional["AuditLogEventType"] = MISSING,
        before: Optional["Snowflake_Type"] = MISSING,
        after: Optional["Snowflake_Type"] = MISSING,
        limit: int = 100,
    ) -> "AuditLog":
        """
        Fetch section of the audit log for this guild.

        Args:
            user_id: The ID of the user to filter by
            action_type: The type of action to filter by
            before: The ID of the entry to start at
            after: The ID of the entry to end at
            limit: The number of entries to return

        Returns:
            An AuditLog object

        """
        data = await self._client.http.get_audit_log(self.id, user_id, action_type, before, after, limit)
        return AuditLog.from_dict(data, self._client)

    def audit_log_history(
        self,
        user_id: Optional["Snowflake_Type"] = MISSING,
        action_type: Optional["AuditLogEventType"] = MISSING,
        before: Optional["Snowflake_Type"] = MISSING,
        after: Optional["Snowflake_Type"] = MISSING,
        limit: int = 100,
    ) -> "AuditLogHistory":
        """
        Get an async iterator for the history of the audit log.

        Args:
            user_id (:class:`Snowflake_Type`): The user ID to search for.
            action_type (:class:`AuditLogEventType`): The action type to search for.
            before: get entries before this message ID
            after: get entries after this message ID
            limit: The maximum number of entries to return (set to 0 for no limit)

        ??? Hint "Example Usage:"
            ```python
            async for entry in guild.audit_log_history(limit=0):
                entry: "AuditLogEntry"
                if entry.changes:
                    # ...
            ```
            or
            ```python
            history = guild.audit_log_history(limit=250)
            # Flatten the async iterator into a list
            entries = await history.flatten()
            ```

        Returns:
            AuditLogHistory (AsyncIterator)

        """
        return AuditLogHistory(self, user_id, action_type, before, after, limit)

    async def edit(
        self,
        name: Absent[Optional[str]] = MISSING,
        description: Absent[Optional[str]] = MISSING,
        verification_level: Absent[Optional["VerificationLevels"]] = MISSING,
        default_message_notifications: Absent[Optional["DefaultNotificationLevels"]] = MISSING,
        explicit_content_filter: Absent[Optional["ExplicitContentFilterLevels"]] = MISSING,
        afk_channel: Absent[Optional[Union["models.GuildVoice", Snowflake_Type]]] = MISSING,
        afk_timeout: Absent[Optional[int]] = MISSING,
        system_channel: Absent[Optional[Union["models.GuildText", Snowflake_Type]]] = MISSING,
        system_channel_flags: Absent[Union[SystemChannelFlags, int]] = MISSING,
        owner: Absent[Optional[Union["models.Member", Snowflake_Type]]] = MISSING,
        icon: Absent[Optional[UPLOADABLE_TYPE]] = MISSING,
        splash: Absent[Optional[UPLOADABLE_TYPE]] = MISSING,
        discovery_splash: Absent[Optional[UPLOADABLE_TYPE]] = MISSING,
        banner: Absent[Optional[UPLOADABLE_TYPE]] = MISSING,
        rules_channel: Absent[Optional[Union["models.GuildText", Snowflake_Type]]] = MISSING,
        public_updates_channel: Absent[Optional[Union["models.GuildText", Snowflake_Type]]] = MISSING,
        preferred_locale: Absent[Optional[str]] = MISSING,
        # ToDo: Fill in guild features. No idea how this works - https://discord.com/developers/docs/resources/guild#guild-object-guild-features
        features: Absent[Optional[list[str]]] = MISSING,
        reason: Absent[Optional[str]] = MISSING,
    ) -> None:
        """
        Edit the guild.

        Args:
            name: The new name of the guild.
            description: The new description of the guild.
            verification_level: The new verification level for the guild.
            default_message_notifications: The new notification level for the guild.
            explicit_content_filter: The new explicit content filter level for the guild.
            afk_channel: The voice channel that should be the new AFK channel.
            afk_timeout: How many seconds does a member need to be afk before they get moved to the AFK channel. Must be either `60`, `300`, `900`, `1800` or `3600`, otherwise HTTPException will be raised.
            icon: The new icon. Requires a bytes like object or a path to an image.
            owner: The new owner of the guild. You, the bot, need to be owner for this to work.
            splash: The new invite splash image. Requires a bytes like object or a path to an image.
            discovery_splash: The new discovery image. Requires a bytes like object or a path to an image.
            banner: The new banner image. Requires a bytes like object or a path to an image.
            system_channel: The text channel where new system messages should appear. This includes boosts and welcome messages.
            system_channel_flags: The new settings for the system channel.
            rules_channel: The text channel where your rules and community guidelines are displayed.
            public_updates_channel: The text channel where updates from discord should appear.
            preferred_locale: The new preferred locale of the guild. Must be an ISO 639 code.
            features: The enabled guild features
            reason: An optional reason for the audit log.

        """
        await self._client.http.modify_guild(
            guild_id=self.id,
            name=name,
            description=description,
            verification_level=int(verification_level) if verification_level else MISSING,
            default_message_notifications=int(default_message_notifications)
            if default_message_notifications
            else MISSING,
            explicit_content_filter=int(explicit_content_filter) if explicit_content_filter else MISSING,
            afk_channel_id=to_snowflake(afk_channel) if afk_channel else MISSING,
            afk_timeout=afk_timeout,
            icon=to_image_data(icon) if icon else MISSING,
            owner_id=to_snowflake(owner) if owner else MISSING,
            splash=to_image_data(splash) if splash else MISSING,
            discovery_splash=to_image_data(discovery_splash) if discovery_splash else MISSING,
            banner=to_image_data(banner) if banner else MISSING,
            system_channel_id=to_snowflake(system_channel) if system_channel else MISSING,
            system_channel_flags=int(system_channel_flags) if system_channel_flags else MISSING,
            rules_channel_id=to_snowflake(rules_channel) if rules_channel else MISSING,
            public_updates_channel_id=to_snowflake(public_updates_channel) if public_updates_channel else MISSING,
            preferred_locale=preferred_locale,
            features=features,
            reason=reason,
        )

    async def create_custom_emoji(
        self,
        name: str,
        imagefile: UPLOADABLE_TYPE,
        roles: Absent[List[Union[Snowflake_Type, "models.Role"]]] = MISSING,
        reason: Absent[Optional[str]] = MISSING,
    ) -> "models.CustomEmoji":
        """
        Create a new custom emoji for the guild.

        Args:
            name: Name of the emoji
            imagefile: The emoji image. (Supports PNG, JPEG, WebP, GIF)
            roles: Roles allowed to use this emoji.
            reason: An optional reason for the audit log.

        Returns:
            The new custom emoji created.

        """
        data_payload = {
            "name": name,
            "image": to_image_data(imagefile),
            "roles": to_snowflake_list(roles) if roles else MISSING,
        }

        emoji_data = await self._client.http.create_guild_emoji(data_payload, self.id, reason=reason)
        return self._client.cache.place_emoji_data(self.id, emoji_data)

    async def create_guild_template(self, name: str, description: Absent[str] = MISSING) -> "models.GuildTemplate":
        """
        Create a new guild template based on this guild.

        Args:
            name: The name of the template (1-100 characters)
            description: The description for the template (0-120 characters)

        Returns:
            The new guild template created.

        """
        template = await self._client.http.create_guild_template(self.id, name, description)
        return GuildTemplate.from_dict(template, self._client)

    async def fetch_guild_templates(self) -> List["models.GuildTemplate"]:
        """
        Fetch all guild templates for this guild.

        Returns:
            A list of guild template objects.

        """
        templates = await self._client.http.get_guild_templates(self.id)
        return GuildTemplate.from_list(templates, self._client)

    async def fetch_all_custom_emojis(self) -> List["models.CustomEmoji"]:
        """
        Gets all the custom emoji present for this guild.

        Returns:
            A list of custom emoji objects.

        """
        emojis_data = await self._client.http.get_all_guild_emoji(self.id)
        return [self._client.cache.place_emoji_data(self.id, emoji_data) for emoji_data in emojis_data]

    async def fetch_custom_emoji(self, emoji_id: Snowflake_Type) -> Optional["models.CustomEmoji"]:
        """
        Fetches the custom emoji present for this guild, based on the emoji id.

        Args:
            emoji_id: The target emoji to get data of.

        Returns:
            The custom emoji object. If the emoji is not found, returns None.

        """
        try:
            return await self._client.cache.fetch_emoji(self.id, emoji_id)
        except NotFound:
            return None

    def get_custom_emoji(self, emoji_id: Snowflake_Type) -> Optional["models.CustomEmoji"]:
        """
        Gets the custom emoji present for this guild, based on the emoji id.

        Args:
            emoji_id: The target emoji to get data of.

        Returns:
            The custom emoji object.

        """
        emoji_id = to_snowflake(emoji_id)
        emoji = self._client.cache.get_emoji(emoji_id)
        if emoji and emoji._guild_id == self.id:
            return emoji
        return None

    async def create_channel(
        self,
        channel_type: Union[ChannelTypes, int],
        name: str,
        topic: Absent[Optional[str]] = MISSING,
        position: Absent[Optional[int]] = MISSING,
        permission_overwrites: Absent[
            Union[dict, "models.PermissionOverwrite", List[Union[dict, "models.PermissionOverwrite"]]]
        ] = MISSING,
        category: Union[Snowflake_Type, "models.GuildCategory"] = None,
        nsfw: bool = False,
        bitrate: int = 64000,
        user_limit: int = 0,
        rate_limit_per_user: int = 0,
        reason: Absent[Optional[str]] = MISSING,
    ) -> "models.TYPE_GUILD_CHANNEL":
        """
        Create a guild channel, allows for explicit channel type setting.

        Args:
            channel_type: The type of channel to create
            name: The name of the channel
            topic: The topic of the channel
            position: The position of the channel in the channel list
            permission_overwrites: Permission overwrites to apply to the channel
            category: The category this channel should be within
            nsfw: Should this channel be marked nsfw
            bitrate: The bitrate of this channel, only for voice
            user_limit: The max users that can be in this channel, only for voice
            rate_limit_per_user: The time users must wait between sending messages
            reason: The reason for creating this channel

        Returns:
            The newly created channel.

        """
        channel_data = await self._client.http.create_guild_channel(
            self.id,
            name,
            channel_type,
            topic,
            position,
            models.process_permission_overwrites(permission_overwrites),
            to_optional_snowflake(category),
            nsfw,
            bitrate,
            user_limit,
            rate_limit_per_user,
            reason,
        )
        return self._client.cache.place_channel_data(channel_data)

    async def create_text_channel(
        self,
        name: str,
        topic: Absent[Optional[str]] = MISSING,
        position: Absent[Optional[int]] = MISSING,
        permission_overwrites: Absent[
            Union[dict, "models.PermissionOverwrite", List[Union[dict, "models.PermissionOverwrite"]]]
        ] = MISSING,
        category: Union[Snowflake_Type, "models.GuildCategory"] = None,
        nsfw: bool = False,
        rate_limit_per_user: int = 0,
        reason: Absent[Optional[str]] = MISSING,
    ) -> "models.GuildText":
        """
        Create a text channel in this guild.

        Args:
            name: The name of the channel
            topic: The topic of the channel
            position: The position of the channel in the channel list
            permission_overwrites: Permission overwrites to apply to the channel
            category: The category this channel should be within
            nsfw: Should this channel be marked nsfw
            rate_limit_per_user: The time users must wait between sending messages
            reason: The reason for creating this channel

        Returns:
           The newly created text channel.

        """
        return await self.create_channel(
            channel_type=ChannelTypes.GUILD_TEXT,
            name=name,
            topic=topic,
            position=position,
            permission_overwrites=permission_overwrites,
            category=category,
            nsfw=nsfw,
            rate_limit_per_user=rate_limit_per_user,
            reason=reason,
        )

    async def create_forum_channel(
        self,
        name: str,
        topic: Absent[Optional[str]] = MISSING,
        position: Absent[Optional[int]] = MISSING,
        permission_overwrites: Absent[
            Union[dict, "models.PermissionOverwrite", List[Union[dict, "models.PermissionOverwrite"]]]
        ] = MISSING,
        category: Union[Snowflake_Type, "models.GuildCategory"] = None,
        nsfw: bool = False,
        rate_limit_per_user: int = 0,
        reason: Absent[Optional[str]] = MISSING,
    ) -> "models.GuildForum":
        """
        Create a forum channel in this guild.

        Args:
            name: The name of the forum channel
            topic: The topic of the forum channel
            position: The position of the forum channel in the channel list
            permission_overwrites: Permission overwrites to apply to the forum channel
            category: The category this forum channel should be within
            nsfw: Should this forum be marked nsfw
            rate_limit_per_user: The time users must wait between sending messages
            reason: The reason for creating this channel

        Returns:
           The newly created forum channel.

        """
        return await self.create_channel(
            channel_type=ChannelTypes.GUILD_FORUM,
            name=name,
            topic=topic,
            position=position,
            permission_overwrites=permission_overwrites,
            category=category,
            nsfw=nsfw,
            rate_limit_per_user=rate_limit_per_user,
            reason=reason,
        )

    async def create_news_channel(
        self,
        name: str,
        topic: Absent[Optional[str]] = MISSING,
        position: Absent[Optional[int]] = MISSING,
        permission_overwrites: Absent[
            Union[dict, "models.PermissionOverwrite", List[Union[dict, "models.PermissionOverwrite"]]]
        ] = MISSING,
        category: Union[Snowflake_Type, "models.GuildCategory"] = None,
        nsfw: bool = False,
        reason: Absent[Optional[str]] = MISSING,
    ) -> "models.GuildNews":
        """
        Create a news channel in this guild.

        Args:
            name: The name of the channel
            topic: The topic of the channel
            position: The position of the channel in the channel list
            permission_overwrites: Permission overwrites to apply to the channel
            category: The category this channel should be within
            nsfw: Should this channel be marked nsfw
            reason: The reason for creating this channel

        Returns:
           The newly created news channel.

        """
        return await self.create_channel(
            channel_type=ChannelTypes.GUILD_NEWS,
            name=name,
            topic=topic,
            position=position,
            permission_overwrites=permission_overwrites,
            category=category,
            nsfw=nsfw,
            reason=reason,
        )

    async def create_voice_channel(
        self,
        name: str,
        topic: Absent[Optional[str]] = MISSING,
        position: Absent[Optional[int]] = MISSING,
        permission_overwrites: Absent[
            Union[dict, "models.PermissionOverwrite", List[Union[dict, "models.PermissionOverwrite"]]]
        ] = MISSING,
        category: Union[Snowflake_Type, "models.GuildCategory"] = None,
        nsfw: bool = False,
        bitrate: int = 64000,
        user_limit: int = 0,
        reason: Absent[Optional[str]] = MISSING,
    ) -> "models.GuildVoice":
        """
        Create a guild voice channel.

        Args:
            name: The name of the channel
            topic: The topic of the channel
            position: The position of the channel in the channel list
            permission_overwrites: Permission overwrites to apply to the channel
            category: The category this channel should be within
            nsfw: Should this channel be marked nsfw
            bitrate: The bitrate of this channel, only for voice
            user_limit: The max users that can be in this channel, only for voice
            reason: The reason for creating this channel

        Returns:
           The newly created voice channel.

        """
        return await self.create_channel(
            channel_type=ChannelTypes.GUILD_VOICE,
            name=name,
            topic=topic,
            position=position,
            permission_overwrites=permission_overwrites,
            category=category,
            nsfw=nsfw,
            bitrate=bitrate,
            user_limit=user_limit,
            reason=reason,
        )

    async def create_stage_channel(
        self,
        name: str,
        topic: Absent[Optional[str]] = MISSING,
        position: Absent[Optional[int]] = MISSING,
        permission_overwrites: Absent[
            Union[dict, "models.PermissionOverwrite", List[Union[dict, "models.PermissionOverwrite"]]]
        ] = MISSING,
        category: Absent[Union[Snowflake_Type, "models.GuildCategory"]] = MISSING,
        bitrate: int = 64000,
        user_limit: int = 0,
        reason: Absent[Optional[str]] = MISSING,
    ) -> "models.GuildStageVoice":
        """
        Create a guild stage channel.

        Args:
            name: The name of the channel
            topic: The topic of the channel
            position: The position of the channel in the channel list
            permission_overwrites: Permission overwrites to apply to the channel
            category: The category this channel should be within
            bitrate: The bitrate of this channel, only for voice
            user_limit: The max users that can be in this channel, only for voice
            reason: The reason for creating this channel

        Returns:
            The newly created stage channel.

        """
        return await self.create_channel(
            channel_type=ChannelTypes.GUILD_STAGE_VOICE,
            name=name,
            topic=topic,
            position=position,
            permission_overwrites=permission_overwrites,
            category=category,
            bitrate=bitrate,
            user_limit=user_limit,
            reason=reason,
        )

    async def create_category(
        self,
        name: str,
        position: Absent[Optional[int]] = MISSING,
        permission_overwrites: Absent[
            Union[dict, "models.PermissionOverwrite", List[Union[dict, "models.PermissionOverwrite"]]]
        ] = MISSING,
        reason: Absent[Optional[str]] = MISSING,
    ) -> "models.GuildCategory":
        """
        Create a category within this guild.

        Args:
            name: The name of the channel
            position: The position of the channel in the channel list
            permission_overwrites: Permission overwrites to apply to the channel
            reason: The reason for creating this channel

        Returns:
            The newly created category.

        """
        return await self.create_channel(
            channel_type=ChannelTypes.GUILD_CATEGORY,
            name=name,
            position=position,
            permission_overwrites=permission_overwrites,
            reason=reason,
        )

    async def delete_channel(
        self, channel: Union["models.TYPE_GUILD_CHANNEL", Snowflake_Type], reason: str = None
    ) -> None:
        """
        Delete the given channel, can handle either a snowflake or channel object.

        This is effectively just an alias for `channel.delete()`

        Args:
            channel: The channel to be deleted
            reason: The reason for this deletion

        """
        if isinstance(channel, (str, int)):
            channel = await self._client.fetch_channel(channel)

        if not channel:
            raise ValueError("Unable to find requested channel")

        if channel.id not in self._channel_ids:
            raise ValueError("This guild does not hold the requested channel")

        await channel.delete(reason)

    async def list_scheduled_events(self, with_user_count: bool = False) -> List["models.ScheduledEvent"]:
        """
        List all scheduled events in this guild.

        Returns:
            A list of scheduled events.

        """
        scheduled_events_data = await self._client.http.list_schedules_events(self.id, with_user_count)
        return models.ScheduledEvent.from_list(scheduled_events_data, self._client)

    async def fetch_scheduled_event(
        self, scheduled_event_id: Snowflake_Type, with_user_count: bool = False
    ) -> Optional["models.ScheduledEvent"]:
        """
        Get a scheduled event by id.

        Args:
            scheduled_event_id: The id of the scheduled event.
            with_user_count: Whether to include the user count in the response.

        Returns:
            The scheduled event. If the event does not exist, returns None.

        """
        try:
            scheduled_event_data = await self._client.http.get_scheduled_event(
                self.id, scheduled_event_id, with_user_count
            )
        except NotFound:
            return None
        return models.ScheduledEvent.from_dict(scheduled_event_data, self._client)

    async def create_scheduled_event(
        self,
        name: str,
        event_type: ScheduledEventType,
        start_time: "models.Timestamp",
        end_time: Absent[Optional["models.Timestamp"]] = MISSING,
        description: Absent[Optional[str]] = MISSING,
        channel_id: Absent[Optional[Snowflake_Type]] = MISSING,
        external_location: Absent[Optional[str]] = MISSING,
        entity_metadata: Optional[dict] = None,
        privacy_level: ScheduledEventPrivacyLevel = ScheduledEventPrivacyLevel.GUILD_ONLY,
        cover_image: Absent[UPLOADABLE_TYPE] = MISSING,
        reason: Absent[Optional[str]] = MISSING,
    ) -> "models.ScheduledEvent":
        """
        Create a scheduled guild event.

        Args:
            name: event name
            event_type: event type
            start_time: `Timestamp` object
            end_time: `Timestamp` object
            description: event description
            channel_id: channel id
            external_location: event external location (For external events)
            entity_metadata: event metadata (additional data for the event)
            privacy_level: event privacy level
            cover_image: the cover image of the scheduled event
            reason: reason for creating this scheduled event

        Returns:
            The newly created ScheduledEvent object

        !!! note
            For external events, external_location is required
            For voice/stage events, channel_id is required

        ??? note
            entity_metadata is the backend dictionary for fluff fields. Where possible, we plan to expose these fields directly.
            The full list of supported fields is https://discord.com/developers/docs/resources/guild-scheduled-event#guild-scheduled-event-object-guild-scheduled-event-entity-metadata
            Example: `entity_metadata=dict(location="cool place")`

        """
        if external_location is not MISSING:
            entity_metadata = {"location": external_location}

        if event_type == ScheduledEventType.EXTERNAL:
            if external_location == MISSING:
                raise EventLocationNotProvided("Location is required for external events")

        payload = {
            "name": name,
            "entity_type": event_type,
            "scheduled_start_time": start_time.isoformat(),
            "scheduled_end_time": end_time.isoformat() if end_time is not MISSING else end_time,
            "description": description,
            "channel_id": channel_id,
            "entity_metadata": entity_metadata,
            "privacy_level": privacy_level,
            "image": to_image_data(cover_image) if cover_image else MISSING,
        }

        scheduled_event_data = await self._client.http.create_scheduled_event(self.id, payload, reason)
        return models.ScheduledEvent.from_dict(scheduled_event_data, self._client)

    async def create_custom_sticker(
        self,
        name: str,
        imagefile: UPLOADABLE_TYPE,
        description: Absent[Optional[str]] = MISSING,
        tags: Absent[Optional[str]] = MISSING,
        reason: Absent[Optional[str]] = MISSING,
    ) -> "models.Sticker":
        """
        Creates a custom sticker for a guild.

        Args:
            name: The name of the sticker (2-30 characters)
            imagefile: The sticker file to upload, must be a PNG, APNG, or Lottie JSON file (max 500 KB)
            description: The description of the sticker (empty or 2-100 characters)
            tags: Autocomplete/suggestion tags for the sticker (max 200 characters)
            reason: Reason for creating the sticker

        Returns:
            New Sticker instance

        """
        payload = {"name": name}

        if description:
            payload["description"] = description

        if tags:
            payload["tags"] = tags

        sticker_data = await self._client.http.create_guild_sticker(payload, self.id, reason)
        return models.Sticker.from_dict(sticker_data, self._client)

    async def fetch_all_custom_stickers(self) -> List["models.Sticker"]:
        """
        Fetches all custom stickers for a guild.

        Returns:
            List of Sticker objects

        """
        stickers_data = await self._client.http.list_guild_stickers(self.id)
        return models.Sticker.from_list(stickers_data, self._client)

    async def fetch_custom_sticker(self, sticker_id: Snowflake_Type) -> Optional["models.Sticker"]:
        """
        Fetches a specific custom sticker for a guild.

        Args:
            sticker_id: ID of sticker to get

        Returns:
            The custom sticker object. If the sticker does not exist, returns None.

        """
        try:
            sticker_data = await self._client.http.get_guild_sticker(self.id, to_snowflake(sticker_id))
        except NotFound:
            return None
        return models.Sticker.from_dict(sticker_data, self._client)

    async def fetch_active_threads(self) -> "models.ThreadList":
        """
        Fetches all active threads in the guild, including public and private threads. Threads are ordered by their id, in descending order.

        Returns:
            List of active threads and thread member object for each returned thread the bot user has joined.

        """
        threads_data = await self._client.http.list_active_threads(self.id)
        return models.ThreadList.from_dict(threads_data, self._client)

    async def fetch_role(self, role_id: Snowflake_Type) -> Optional["models.Role"]:
        """
        Fetch the specified role by ID.

        Args:
            role_id: The ID of the role to get

        Returns:
            The role object. If the role does not exist, returns None.

        """
        try:
            return await self._client.cache.fetch_role(self.id, role_id)
        except NotFound:
            return None

    def get_role(self, role_id: Snowflake_Type) -> Optional["models.Role"]:
        """
        Get the specified role by ID.

        Args:
            role_id: The ID of the role to get

        Returns:
            A role object or None if the role is not found.

        """
        role_id = to_snowflake(role_id)
        if role_id in self._role_ids:
            return self._client.cache.get_role(role_id)
        return None

    async def create_role(
        self,
        name: Absent[Optional[str]] = MISSING,
        permissions: Absent[Optional[Permissions]] = MISSING,
        colour: Absent[Optional[Union["models.Color", int]]] = MISSING,
        color: Absent[Optional[Union["models.Color", int]]] = MISSING,
        hoist: Optional[bool] = False,
        mentionable: Optional[bool] = False,
        icon: Absent[Optional[UPLOADABLE_TYPE]] = MISSING,
        reason: Absent[Optional[str]] = MISSING,
    ) -> "models.Role":
        """
        Create a new role for the guild. You must have the `manage roles` permission.

        Args:
            name: The name the role should have. `Default: new role`
            permissions: The permissions the role should have. `Default: @everyone permissions`
            colour: The colour of the role. Can be either `Color` or an RGB integer. `Default: BrandColors.BLACK`
            color: Alias for `colour`
            icon: Can be either a bytes like object or a path to an image, or a unicode emoji which is supported by discord.
            hoist: Whether the role is shown separately in the members list. `Default: False`
            mentionable: Whether the role can be mentioned. `Default: False`
            reason: An optional reason for the audit log.

        Returns:
            A role object or None if the role is not found.

        """
        payload = {}

        if name:
            payload.update({"name": name})

        if permissions:
            payload.update({"permissions": str(int(permissions))})

        colour = colour or color
        if colour:
            payload.update({"color": colour.value})

        if hoist:
            payload.update({"hoist": True})

        if mentionable:
            payload.update({"mentionable": True})

        if icon:
            # test if the icon is probably a unicode emoji (str and len() == 1) or a path / bytes obj
            if isinstance(icon, str) and len(icon) == 1:
                payload.update({"unicode_emoji": icon})

            else:
                payload.update({"icon": to_image_data(icon)})

        result = await self._client.http.create_guild_role(guild_id=self.id, payload=payload, reason=reason)
        return self._client.cache.place_role_data(guild_id=self.id, data=[result])[to_snowflake(result["id"])]

    def get_channel(self, channel_id: Snowflake_Type) -> Optional["models.TYPE_GUILD_CHANNEL"]:
        """
        Returns a channel with the given `channel_id`.

        Args:
            channel_id: The ID of the channel to get

        Returns:
            Channel object if found, otherwise None

        """
        channel_id = to_snowflake(channel_id)
        if channel_id in self._channel_ids:
            # theoretically, this could get any channel the client can see,
            # but to make it less confusing to new programmers,
            # i intentionally check that the guild contains the channel first
            return self._client.cache.get_channel(channel_id)
        return None

    async def fetch_channel(self, channel_id: Snowflake_Type) -> Optional["models.TYPE_GUILD_CHANNEL"]:
        """
        Returns a channel with the given `channel_id` from the API.

        Args:
            channel_id: The ID of the channel to get

        Returns:
            The channel object. If the channel does not exist, returns None.

        """
        channel_id = to_snowflake(channel_id)
        if channel_id in self._channel_ids or not self._client.gateway_started:
            # The latter check here is to see if the bot is running with the gateway.
            # If not, then we need to check the API since only the gateway
            # populates the channel IDs

            # theoretically, this could get any channel the client can see,
            # but to make it less confusing to new programmers,
            # i intentionally check that the guild contains the channel first
            try:
                channel = await self._client.fetch_channel(channel_id)
                if channel._guild_id == self.id:
                    return channel
            except (NotFound, AttributeError):
                return None

        return None

    def get_thread(self, thread_id: Snowflake_Type) -> Optional["models.TYPE_THREAD_CHANNEL"]:
        """
        Returns a Thread with the given `thread_id`.

        Args:
            thread_id: The ID of the thread to get

        Returns:
            Thread object if found, otherwise None

        """
        thread_id = to_snowflake(thread_id)
        if thread_id in self._thread_ids:
            return self._client.cache.get_channel(thread_id)
        return None

    async def fetch_thread(self, thread_id: Snowflake_Type) -> Optional["models.TYPE_THREAD_CHANNEL"]:
        """
        Returns a Thread with the given `thread_id` from the API.

        Args:
            thread_id: The ID of the thread to get

        Returns:
            Thread object if found, otherwise None

        """
        thread_id = to_snowflake(thread_id)
        if thread_id in self._thread_ids:
            try:
                return await self._client.fetch_channel(thread_id)
            except NotFound:
                return None
        return None

    async def prune_members(
        self,
        days: int = 7,
        roles: Optional[List[Snowflake_Type]] = None,
        compute_prune_count: bool = True,
        reason: Absent[str] = MISSING,
    ) -> Optional[int]:
        """
        Begin a guild prune. Removes members from the guild who who have not interacted for the last `days` days. By default, members with roles are excluded from pruning, to include them, pass their role (or role id) in `roles` Requires `kick members` permission.

        Args:
            days: number of days to prune (1-30)
            roles: list of roles to include in the prune
            compute_prune_count: Whether the number of members pruned should be calculated (disable this for large guilds)
            reason: The reason for this prune

        Returns:
            The total number of members pruned, if `compute_prune_count` is set to True, otherwise None

        """
        if roles:
            roles = [str(to_snowflake(r)) for r in roles]

        resp = await self._client.http.begin_guild_prune(
            self.id, days, include_roles=roles, compute_prune_count=compute_prune_count, reason=reason
        )
        return resp["pruned"]

    async def estimate_prune_members(
        self, days: int = 7, roles: List[Union[Snowflake_Type, "models.Role"]] = MISSING
    ) -> int:
        """
        Calculate how many members would be pruned, should `guild.prune_members` be used. By default, members with roles are excluded from pruning, to include them, pass their role (or role id) in `roles`.

        Args:
            days: number of days to prune (1-30)
            roles: list of roles to include in the prune

        Returns:
            Total number of members that would be pruned

        """
        if roles is not MISSING:
            roles = [r.id if isinstance(r, models.Role) else r for r in roles]
        else:
            roles = []

        resp = await self._client.http.get_guild_prune_count(self.id, days=days, include_roles=roles)
        return resp["pruned"]

    async def leave(self) -> None:
        """Leave this guild."""
        await self._client.http.leave_guild(self.id)

    async def delete(self) -> None:
        """
        Delete the guild.

        !!! note
            You must own this guild to do this.

        """
        await self._client.http.delete_guild(self.id)

    async def kick(
        self, user: Union["models.User", "models.Member", Snowflake_Type], reason: Absent[str] = MISSING
    ) -> None:
        """
        Kick a user from the guild.

        !!! note
            You must have the `kick members` permission

        Args:
            user: The user to kick
            reason: The reason for the kick

        """
        await self._client.http.remove_guild_member(self.id, to_snowflake(user), reason=reason)

    async def ban(
        self,
        user: Union["models.User", "models.Member", Snowflake_Type],
        delete_message_days: Absent[int] = MISSING,
        delete_message_seconds: int = 0,
        reason: Absent[str] = MISSING,
    ) -> None:
        """
        Ban a user from the guild.

        !!! note
            You must have the `ban members` permission

        Args:
            user: The user to ban
            delete_message_days: (deprecated) How many days worth of messages to remove
            delete_message_seconds: How many seconds worth of messages to remove
            reason: The reason for the ban

        """
        if delete_message_days is not MISSING:
            warn("delete_message_days is deprecated and will be removed in a future update", DeprecationWarning)
            delete_message_seconds = delete_message_days * 3600
        await self._client.http.create_guild_ban(self.id, to_snowflake(user), delete_message_seconds, reason=reason)

    async def fetch_ban(self, user: Union["models.User", "models.Member", Snowflake_Type]) -> Optional[GuildBan]:
        """
        Fetches the ban information for the specified user in the guild. You must have the `ban members` permission.

        Args:
            user: The user to look up.

        Returns:
            The ban information. If the user is not banned, returns None.

        """
        try:
            ban_info = await self._client.http.get_guild_ban(self.id, to_snowflake(user))
        except NotFound:
            return None
        return GuildBan(reason=ban_info["reason"], user=self._client.cache.place_user_data(ban_info["user"]))

    async def fetch_bans(
        self,
        before: Optional["Snowflake_Type"] = MISSING,
        after: Optional["Snowflake_Type"] = MISSING,
        limit: int = 1000,
    ) -> list[GuildBan]:
        """
        Fetches bans for the guild. You must have the `ban members` permission.

        Args:
            before: consider only users before given user id
            after: consider only users after given user id
            limit: number of users to return (up to maximum 1000)

        Returns:
            A list containing bans and information about them.

        """
        ban_infos = await self._client.http.get_guild_bans(self.id, before=before, after=after, limit=limit)
        return [
            GuildBan(reason=ban_info["reason"], user=self._client.cache.place_user_data(ban_info["user"]))
            for ban_info in ban_infos
        ]

    async def create_auto_moderation_rule(
        self,
        name: str,
        *,
        trigger: BaseTrigger,
        actions: list[BaseAction],
        exempt_roles: list["Snowflake_Type"] = MISSING,
        exempt_channels: list["Snowflake_Type"] = MISSING,
        enabled: bool = True,
        event_type: AutoModEvent = AutoModEvent.MESSAGE_SEND,
    ) -> AutoModRule:
        """
        Create an auto-moderation rule in this guild.

        Args:
            name: The name of the rule
            trigger: The trigger for this rule
            actions: A list of actions to take upon triggering
            exempt_roles: Roles that ignore this rule
            exempt_channels: Channels that ignore this role
            enabled: Is this rule enabled?
            event_type: The type of event that triggers this rule

        Returns:
            The created rule
        """
        rule = AutoModRule(
            name=name,
            enabled=enabled,
            actions=actions,
            event_type=event_type,
            trigger=trigger,
            exempt_channels=exempt_channels if exempt_roles is not MISSING else [],
            exempt_roles=exempt_roles if exempt_roles is not MISSING else [],
            client=self._client,
        )
        data = await self._client.http.create_auto_moderation_rule(self.id, rule.to_dict())
        return AutoModRule.from_dict(data, self._client)

    async def fetch_auto_moderation_rules(self) -> List[AutoModRule]:
        """
        Get this guild's auto moderation rules.

        Returns:
            A list of auto moderation rules
        """
        data = await self._client.http.get_auto_moderation_rules(self.id)
        return [AutoModRule.from_dict(d, self._client) for d in data]

    async def delete_auto_moderation_rule(self, rule: "Snowflake_Type", reason: Absent[str] = MISSING) -> None:
        """
        Delete a given auto moderation rule.

        Args:
            rule: The rule to delete
            reason: The reason for deleting this rule
        """
        await self._client.http.delete_auto_moderation_rule(self.id, to_snowflake(rule), reason=reason)

    async def modify_auto_moderation_rule(
        self,
        rule: "Snowflake_Type",
        *,
        name: Absent[str] = MISSING,
        trigger: Absent[BaseTrigger] = MISSING,
        trigger_type: Absent[AutoModTriggerType] = MISSING,
        trigger_metadata: Absent[dict] = MISSING,
        actions: Absent[list[BaseAction]] = MISSING,
        exempt_channels: Absent[list["Snowflake_Type"]] = MISSING,
        exempt_roles: Absent[list["Snowflake_Type"]] = MISSING,
        event_type: Absent[AutoModEvent] = MISSING,
        enabled: Absent[bool] = MISSING,
        reason: Absent[str] = MISSING,
    ) -> AutoModRule:
        """
        Modify an existing automod rule.

        Args:
            rule: The rule to modify
            name: The name of the rule
            trigger: A trigger for this rule
            trigger_type: The type trigger for this rule (ignored if trigger specified)
            trigger_metadata: Metadata for the trigger (ignored if trigger specified)
            actions: A list of actions to take upon triggering
            exempt_roles: Roles that ignore this rule
            exempt_channels: Channels that ignore this role
            enabled: Is this rule enabled?
            event_type: The type of event that triggers this rule
            reason: The reason for this change

        Returns:
            The updated rule
        """
        if trigger:
            _data = trigger.to_dict()
            trigger_type = _data["trigger_type"]
            trigger_metadata = _data.get("trigger_metadata", {})

        out = await self._client.http.modify_auto_moderation_rule(
            self.id,
            to_snowflake(rule),
            name=name,
            trigger_type=trigger_type,
            trigger_metadata=trigger_metadata,
            actions=actions,
            exempt_roles=to_snowflake_list(exempt_roles) if exempt_roles is not MISSING else MISSING,
            exempt_channels=to_snowflake_list(exempt_channels) if exempt_channels is not MISSING else MISSING,
            event_type=event_type,
            enabled=enabled,
            reason=reason,
        )
        return AutoModRule.from_dict(out, self._client)

    async def unban(
        self, user: Union["models.User", "models.Member", Snowflake_Type], reason: Absent[str] = MISSING
    ) -> None:
        """
        Unban a user from the guild.

        !!! note
            You must have the `ban members` permission

        Args:
            user: The user to unban
            reason: The reason for the ban

        """
        await self._client.http.remove_guild_ban(self.id, to_snowflake(user), reason=reason)

    async def fetch_widget_image(self, style: str = None) -> str:
        """
        Fetch a guilds widget image.

        For a list of styles, look here: https://discord.com/developers/docs/resources/guild#get-guild-widget-image-widget-style-options

        Args:
            style: The style to use for the widget image

        Returns:
            The URL of the widget image.

        """
        return await self._client.http.get_guild_widget_image(self.id, style)

    async def fetch_widget_settings(self) -> "GuildWidgetSettings":
        """
        Fetches the guilds widget settings.

        Returns:
            The guilds widget settings object.

        """
        return await GuildWidgetSettings.from_dict(await self._client.http.get_guild_widget_settings(self.id))

    async def fetch_widget(self) -> "GuildWidget":
        """
        Fetches the guilds widget.

        Returns:
            The guilds widget object.

        """
        return GuildWidget.from_dict(await self._client.http.get_guild_widget(self.id), self._client)

    async def modify_widget(
        self,
        enabled: Absent[bool] = MISSING,
        channel: Absent[Union["models.TYPE_GUILD_CHANNEL", Snowflake_Type]] = MISSING,
        settings: Absent["GuildWidgetSettings"] = MISSING,
    ) -> "GuildWidget":
        """
        Modify the guild's widget.

        Args:
            enabled: Should the widget be enabled?
            channel: The channel to use in the widget
            settings: The settings to use for the widget

        Returns:
            The updated guilds widget object.

        """
        if isinstance(settings, GuildWidgetSettings):
            enabled = settings.enabled
            channel = settings.channel_id

        channel = to_optional_snowflake(channel)
        return GuildWidget.from_dict(
            await self._client.http.modify_guild_widget(self.id, enabled, channel), self._client
        )

    async def fetch_invites(self) -> List["models.Invite"]:
        """
        Fetches all invites for the guild.

        Returns:
            A list of invites for the guild.

        """
        invites_data = await self._client.http.get_guild_invites(self.id)
        return models.Invite.from_list(invites_data, self._client)

    async def fetch_guild_integrations(self) -> List["models.GuildIntegration"]:
        """
        Fetches all integrations for the guild.

        Returns:
            A list of integrations for the guild.

        """
        data = await self._client.http.get_guild_integrations(self.id)
        return [GuildIntegration.from_dict(d | {"guild_id": self.id}, self._client) for d in data]

    async def search_members(self, query: str, limit: int = 1) -> List["models.Member"]:
        """
        Search for members in the guild whose username or nickname starts with a provided string.

        Args:
            query: Query string to match username(s) and nickname(s) against.
            limit: Max number of members to return (1-1000)

        Returns:
            A list of members matching the query.

        """
        data = await self._client.http.search_guild_members(guild_id=self.id, query=query, limit=limit)
        return [self._client.cache.place_member_data(self.id, _d) for _d in data]

    async def fetch_voice_regions(self) -> List["models.VoiceRegion"]:
        """
        Fetches the voice regions for the guild.

        Unlike the `NAFF.fetch_voice_regions` method, this will returns VIP servers when the guild is VIP-enabled.

        Returns:
            A list of voice regions.

        """
        regions_data = await self._client.http.get_guild_voice_regions(self.id)
        regions = models.VoiceRegion.from_list(regions_data)
        return regions

    @property
    def gui_sorted_channels(self) -> list["models.TYPE_GUILD_CHANNEL"]:
        """Return this guilds channels sorted by their gui positions"""
        # create a sorted list of objects by their gui position
        if not self._channel_gui_positions:
            self._calculate_gui_channel_positions()
        return [
            self._client.get_channel(k)
            for k, v in sorted(self._channel_gui_positions.items(), key=lambda item: item[1])
        ]

    def get_channel_gui_position(self, channel_id: "Snowflake_Type") -> int:
        """
        Get a given channels gui position.

        Args:
            channel_id: The ID of the channel to get the gui position for.

        Returns:
            The gui position of the channel.
        """
        if not self._channel_gui_positions:
            self._calculate_gui_channel_positions()
        return self._channel_gui_positions.get(to_snowflake(channel_id), 0)

    def _calculate_gui_channel_positions(self) -> list["models.TYPE_GUILD_CHANNEL"]:
        """
        Calculates the GUI position for all known channels within this guild.

        Note this is an expensive operation and should only be run when actually required.

        Returns:
            The list of channels in this guild, sorted by their GUI position.
        """
        # sorting is based on this https://github.com/discord/discord-api-docs/issues/4613#issuecomment-1059997612
        sort_map = {
            ChannelTypes.GUILD_NEWS_THREAD: 1,
            ChannelTypes.GUILD_PUBLIC_THREAD: 1,
            ChannelTypes.GUILD_PRIVATE_THREAD: 1,
            ChannelTypes.GUILD_TEXT: 2,
            ChannelTypes.GUILD_CATEGORY: 2,
            ChannelTypes.GUILD_NEWS: 2,
            ChannelTypes.GUILD_FORUM: 2,  # assumed value
            ChannelTypes.GUILD_VOICE: 3,
            ChannelTypes.GUILD_STAGE_VOICE: 3,
        }

        def channel_sort_func(a, b) -> int:
            a_sorting = sort_map.get(a.type, 0)
            b_sorting = sort_map.get(b.type, 0)

            if a_sorting != b_sorting:
                return a_sorting - b_sorting
            return a.position - b.position or a.id - b.id

        sorted_channels = sorted(self.channels, key=cmp_to_key(channel_sort_func))

        for channel in sorted_channels[::-1]:
            if channel.parent_id:
                # sort channels under their respective categories
                sorted_channels.remove(channel)
                parent_index = sorted_channels.index(channel.category)
                sorted_channels.insert(parent_index + 1, channel)
            elif channel.type != ChannelTypes.GUILD_CATEGORY:
                # move non-category channels to the top
                sorted_channels.remove(channel)
                sorted_channels.insert(0, channel)

        self._channel_gui_positions = {channel.id: i for i, channel in enumerate(sorted_channels)}

        return sorted_channels

unavailable: bool = field(default=False) class-attribute

True if this guild is unavailable due to an outage.

afk_channel_id: Optional[Snowflake_Type] = field(default=None) class-attribute

The channel id for afk.

afk_timeout: Optional[int] = field(default=None) class-attribute

afk timeout in seconds.

widget_enabled: bool = field(default=False) class-attribute

True if the server widget is enabled.

widget_channel_id: Optional[Snowflake_Type] = field(default=None) class-attribute

The channel id that the widget will generate an invite to, or None if set to no invite.

verification_level: Union[VerificationLevels, int] = field(default=VerificationLevels.NONE) class-attribute

The verification level required for the guild.

default_message_notifications: Union[DefaultNotificationLevels, int] = field(default=DefaultNotificationLevels.ALL_MESSAGES) class-attribute

The default message notifications level.

explicit_content_filter: Union[ExplicitContentFilterLevels, int] = field(default=ExplicitContentFilterLevels.DISABLED) class-attribute

The explicit content filter level.

mfa_level: Union[MFALevels, int] = field(default=MFALevels.NONE) class-attribute

The required MFA (Multi Factor Authentication) level for the guild.

system_channel_id: Optional[Snowflake_Type] = field(default=None) class-attribute

The id of the channel where guild notices such as welcome messages and boost events are posted.

system_channel_flags: SystemChannelFlags = field(default=SystemChannelFlags.NONE, converter=SystemChannelFlags) class-attribute

The system channel flags.

rules_channel_id: Optional[Snowflake_Type] = field(default=None) class-attribute

The id of the channel where Community guilds can display rules and/or guidelines.

joined_at: str = field(default=None, converter=optional(timestamp_converter)) class-attribute

When this guild was joined at.

large: bool = field(default=False) class-attribute

True if this is considered a large guild.

member_count: int = field(default=0) class-attribute

The total number of members in this guild.

presences: List[dict] = field(factory=list) class-attribute

The presences of the members in the guild, will only include non-offline members if the size is greater than large threshold.

max_presences: Optional[int] = field(default=None) class-attribute

The maximum number of presences for the guild. (None is always returned, apart from the largest of guilds)

max_members: Optional[int] = field(default=None) class-attribute

The maximum number of members for the guild.

vanity_url_code: Optional[str] = field(default=None) class-attribute

The vanity url code for the guild.

banner: Optional[str] = field(default=None) class-attribute

Hash for banner image.

premium_tier: Optional[str] = field(default=None) class-attribute

The premium tier level. (Server Boost level)

premium_subscription_count: int = field(default=0) class-attribute

The number of boosts this guild currently has.

preferred_locale: str = field() class-attribute

The preferred locale of a Community guild. Used in server discovery and notices from Discord. Defaults to "en-US"

public_updates_channel_id: Optional[Snowflake_Type] = field(default=None) class-attribute

The id of the channel where admins and moderators of Community guilds receive notices from Discord.

max_video_channel_users: int = field(default=0) class-attribute

The maximum amount of users in a video channel.

welcome_screen: Optional[GuildWelcome] = field(default=None) class-attribute

The welcome screen of a Community guild, shown to new members, returned in an Invite's guild object.

nsfw_level: Union[NSFWLevels, int] = field(default=NSFWLevels.DEFAULT) class-attribute

The guild NSFW level.

stage_instances: List[dict] = field(factory=list) class-attribute

Stage instances in the guild.

chunked = field(factory=asyncio.Event, metadata=no_export_meta) class-attribute

An event that is fired when this guild has been chunked

create(name, client, *, icon=MISSING, verification_level=MISSING, default_message_notifications=MISSING, explicit_content_filter=MISSING, roles=MISSING, channels=MISSING, afk_channel_id=MISSING, afk_timeout=MISSING, system_channel_id=MISSING, system_channel_flags=MISSING) classmethod async

Create a guild.

Note

This method will only work for bots in less than 10 guilds.

Param notes

Roles: - When using the roles parameter, the first member of the array is used to change properties of the guild's @everyone role. If you are trying to bootstrap a guild with additional roles, keep this in mind. - When using the roles parameter, the required id field within each role object is an integer placeholder, and will be replaced by the API upon consumption. Its purpose is to allow you to overwrite a role's permissions in a channel when also passing in channels with the channels array.

Channels: - When using the channels parameter, the position field is ignored, and none of the default channels are created. - When using the channels parameter, the id field within each channel object may be set to an integer placeholder, and will be replaced by the API upon consumption. Its purpose is to allow you to create GUILD_CATEGORY channels by setting the parent_id field on any children to the category's id field. Category channels must be listed before any children.

Parameters:

Name Type Description Default
name str

name of the guild (2-100 characters)

required
client Client

The NAFF client

required
icon Absent[Optional[UPLOADABLE_TYPE]]

An icon for the guild

MISSING
verification_level Absent[VerificationLevels]

The guild's verification level

MISSING
default_message_notifications Absent[DefaultNotificationLevels]

The default message notification level

MISSING
explicit_content_filter Absent[ExplicitContentFilterLevels]

The guild's explicit content filter level

MISSING
roles Absent[list[dict]]

An array of partial role dictionaries

MISSING
channels Absent[list[dict]]

An array of partial channel dictionaries

MISSING
afk_channel_id Absent[Snowflake_Type]

id for afk channel

MISSING
afk_timeout Absent[int]

afk timeout in seconds

MISSING
system_channel_id Absent[Snowflake_Type]

the id of the channel where guild notices should go

MISSING
system_channel_flags Absent[SystemChannelFlags]

flags for the system channel

MISSING

Returns:

Type Description
Guild

The created guild object

Source code in naff/models/discord/guild.py
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
@classmethod
async def create(
    cls,
    name: str,
    client: "Client",
    *,
    icon: Absent[Optional[UPLOADABLE_TYPE]] = MISSING,
    verification_level: Absent["VerificationLevels"] = MISSING,
    default_message_notifications: Absent["DefaultNotificationLevels"] = MISSING,
    explicit_content_filter: Absent["ExplicitContentFilterLevels"] = MISSING,
    roles: Absent[list[dict]] = MISSING,
    channels: Absent[list[dict]] = MISSING,
    afk_channel_id: Absent["Snowflake_Type"] = MISSING,
    afk_timeout: Absent[int] = MISSING,
    system_channel_id: Absent["Snowflake_Type"] = MISSING,
    system_channel_flags: Absent["SystemChannelFlags"] = MISSING,
) -> "Guild":
    """
    Create a guild.

    !!! note
        This method will only work for bots in less than 10 guilds.

    ??? note "Param notes"
        Roles:
            - When using the `roles` parameter, the first member of the array is used to change properties of the guild's `@everyone` role. If you are trying to bootstrap a guild with additional roles, keep this in mind.
            - When using the `roles` parameter, the required id field within each role object is an integer placeholder, and will be replaced by the API upon consumption. Its purpose is to allow you to overwrite a role's permissions in a channel when also passing in channels with the channels array.

        Channels:
            - When using the `channels` parameter, the position field is ignored, and none of the default channels are created.
            - When using the `channels` parameter, the id field within each channel object may be set to an integer placeholder, and will be replaced by the API upon consumption. Its purpose is to allow you to create `GUILD_CATEGORY` channels by setting the `parent_id` field on any children to the category's id field. Category channels must be listed before any children.

    Args:
        name: name of the guild (2-100 characters)
        client: The NAFF client
        icon: An icon for the guild
        verification_level: The guild's verification level
        default_message_notifications: The default message notification level
        explicit_content_filter: The guild's explicit content filter level
        roles: An array of partial role dictionaries
        channels: An array of partial channel dictionaries
        afk_channel_id: id for afk channel
        afk_timeout: afk timeout in seconds
        system_channel_id: the id of the channel where guild notices should go
        system_channel_flags: flags for the system channel

    Returns:
        The created guild object

    """
    data = await client.http.create_guild(
        name=name,
        icon=to_image_data(icon) if icon else MISSING,
        verification_level=verification_level,
        default_message_notifications=default_message_notifications,
        explicit_content_filter=explicit_content_filter,
        roles=roles,
        channels=channels,
        afk_channel_id=afk_channel_id,
        afk_timeout=afk_timeout,
        system_channel_id=system_channel_id,
        system_channel_flags=int(system_channel_flags) if system_channel_flags else MISSING,
    )
    return client.cache.place_guild_data(data)

channels() property

Returns a list of channels associated with this guild.

Source code in naff/models/discord/guild.py
314
315
316
317
@property
def channels(self) -> List["models.TYPE_GUILD_CHANNEL"]:
    """Returns a list of channels associated with this guild."""
    return [self._client.cache.get_channel(c_id) for c_id in self._channel_ids]

threads() property

Returns a list of threads associated with this guild.

Source code in naff/models/discord/guild.py
319
320
321
322
@property
def threads(self) -> List["models.TYPE_THREAD_CHANNEL"]:
    """Returns a list of threads associated with this guild."""
    return [self._client.cache.get_channel(t_id) for t_id in self._thread_ids]

members() property

Returns a list of all members within this guild.

Source code in naff/models/discord/guild.py
324
325
326
327
@property
def members(self) -> List["models.Member"]:
    """Returns a list of all members within this guild."""
    return [self._client.cache.get_member(self.id, m_id) for m_id in self._member_ids]

premium_subscribers() property

Returns a list of all premium subscribers

Source code in naff/models/discord/guild.py
329
330
331
332
@property
def premium_subscribers(self) -> List["models.Member"]:
    """Returns a list of all premium subscribers"""
    return [member for member in self.members if member.premium]

bots() property

Returns a list of all bots within this guild

Source code in naff/models/discord/guild.py
334
335
336
337
@property
def bots(self) -> List["models.Member"]:
    """Returns a list of all bots within this guild"""
    return [member for member in self.members if member.bot]

humans() property

Returns a list of all humans within this guild

Source code in naff/models/discord/guild.py
339
340
341
342
@property
def humans(self) -> List["models.Member"]:
    """Returns a list of all humans within this guild"""
    return [member for member in self.members if not member.bot]

roles() property

Returns a list of roles associated with this guild.

Source code in naff/models/discord/guild.py
344
345
346
347
@property
def roles(self) -> List["models.Role"]:
    """Returns a list of roles associated with this guild."""
    return [self._client.cache.get_role(r_id) for r_id in self._role_ids]

me() property

Returns this bots member object within this guild.

Source code in naff/models/discord/guild.py
349
350
351
352
@property
def me(self) -> "models.Member":
    """Returns this bots member object within this guild."""
    return self._client.cache.get_member(self.id, self._client.user.id)

system_channel() property

Returns the channel this guild uses for system messages.

Source code in naff/models/discord/guild.py
354
355
356
357
@property
def system_channel(self) -> Optional["models.GuildText"]:
    """Returns the channel this guild uses for system messages."""
    return self._client.cache.get_channel(self.system_channel_id)

rules_channel() property

Returns the channel declared as a rules channel.

Source code in naff/models/discord/guild.py
359
360
361
362
@property
def rules_channel(self) -> Optional["models.GuildText"]:
    """Returns the channel declared as a rules channel."""
    return self._client.cache.get_channel(self.rules_channel_id)

public_updates_channel() property

Returns the channel where server staff receive notices from Discord.

Source code in naff/models/discord/guild.py
364
365
366
367
@property
def public_updates_channel(self) -> Optional["models.GuildText"]:
    """Returns the channel where server staff receive notices from Discord."""
    return self._client.cache.get_channel(self.public_updates_channel_id)

emoji_limit() property

The maximum number of emoji this guild can have.

Source code in naff/models/discord/guild.py
369
370
371
372
373
@property
def emoji_limit(self) -> int:
    """The maximum number of emoji this guild can have."""
    base = 200 if "MORE_EMOJI" in self.features else 50
    return max(base, PREMIUM_GUILD_LIMITS[self.premium_tier]["emoji"])

sticker_limit() property

The maximum number of stickers this guild can have.

Source code in naff/models/discord/guild.py
375
376
377
378
379
@property
def sticker_limit(self) -> int:
    """The maximum number of stickers this guild can have."""
    base = 60 if "MORE_STICKERS" in self.features else 0
    return max(base, PREMIUM_GUILD_LIMITS[self.premium_tier]["stickers"])

bitrate_limit() property

The maximum bitrate for this guild.

Source code in naff/models/discord/guild.py
381
382
383
384
385
@property
def bitrate_limit(self) -> int:
    """The maximum bitrate for this guild."""
    base = 128000 if "VIP_REGIONS" in self.features else 96000
    return max(base, PREMIUM_GUILD_LIMITS[self.premium_tier]["bitrate"])

filesize_limit() property

The maximum filesize that may be uploaded within this guild.

Source code in naff/models/discord/guild.py
387
388
389
390
@property
def filesize_limit(self) -> int:
    """The maximum filesize that may be uploaded within this guild."""
    return PREMIUM_GUILD_LIMITS[self.premium_tier]["filesize"]

default_role() property

The @everyone role in this guild.

Source code in naff/models/discord/guild.py
392
393
394
395
@property
def default_role(self) -> "models.Role":
    """The `@everyone` role in this guild."""
    return self._client.cache.get_role(self.id)  # type: ignore

premium_subscriber_role() property

The role given to boosters of this server, if set.

Source code in naff/models/discord/guild.py
397
398
399
400
401
402
403
@property
def premium_subscriber_role(self) -> Optional["models.Role"]:
    """The role given to boosters of this server, if set."""
    for role in self.roles:
        if role.premium_subscriber:
            return role
    return None

my_role() property

The role associated with this client, if set.

Source code in naff/models/discord/guild.py
405
406
407
408
409
410
411
412
@property
def my_role(self) -> Optional["models.Role"]:
    """The role associated with this client, if set."""
    m_r_id = self._client.user.id
    for role in self.roles:
        if role._bot_id == m_r_id:
            return role
    return None

permissions() property

Alias for me.guild_permissions

Source code in naff/models/discord/guild.py
414
415
416
417
@property
def permissions(self) -> Permissions:
    """Alias for me.guild_permissions"""
    return self.me.guild_permissions

voice_state() property

Get the bot's voice state for the guild.

Source code in naff/models/discord/guild.py
419
420
421
422
@property
def voice_state(self) -> Optional["models.VoiceState"]:
    """Get the bot's voice state for the guild."""
    return self._client.cache.get_bot_voice_state(self.id)

voice_states() property

Get a list of the active voice states in this guild.

Source code in naff/models/discord/guild.py
424
425
426
427
428
429
430
@property
def voice_states(self) -> List["models.VoiceState"]:
    """Get a list of the active voice states in this guild."""
    # this is *very* ick, but we cache by user_id, so we have to do it this way,
    # alternative would be maintaining a lookup table in this guild object, which is inherently unreliable
    # noinspection PyProtectedMember
    return [v_state for v_state in self._client.cache.voice_state_cache.values() if v_state._guild_id == self.id]

fetch_member(member_id) async

Return the Member with the given discord ID, fetching from the API if necessary.

Parameters:

Name Type Description Default
member_id Snowflake_Type

The ID of the member.

required

Returns:

Type Description
Optional[Member]

The member object fetched. If the member is not in this guild, returns None.

Source code in naff/models/discord/guild.py
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
async def fetch_member(self, member_id: Snowflake_Type) -> Optional["models.Member"]:
    """
    Return the Member with the given discord ID, fetching from the API if necessary.

    Args:
        member_id: The ID of the member.

    Returns:
        The member object fetched. If the member is not in this guild, returns None.

    """
    try:
        return await self._client.cache.fetch_member(self.id, member_id)
    except NotFound:
        return None

get_member(member_id)

Return the Member with the given discord ID.

Parameters:

Name Type Description Default
member_id Snowflake_Type

The ID of the member

required

Returns:

Type Description
Optional[Member]

Member object or None

Source code in naff/models/discord/guild.py
448
449
450
451
452
453
454
455
456
457
458
459
def get_member(self, member_id: Snowflake_Type) -> Optional["models.Member"]:
    """
    Return the Member with the given discord ID.

    Args:
        member_id: The ID of the member

    Returns:
        Member object or None

    """
    return self._client.cache.get_member(self.id, member_id)

fetch_owner() async

Return the Guild owner, fetching from the API if necessary.

Returns:

Type Description
Member

Member object or None

Source code in naff/models/discord/guild.py
461
462
463
464
465
466
467
468
469
async def fetch_owner(self) -> "models.Member":
    """
    Return the Guild owner, fetching from the API if necessary.

    Returns:
        Member object or None

    """
    return await self._client.cache.fetch_member(self.id, self._owner_id)

get_owner()

Return the Guild owner

Returns:

Type Description
Member

Member object or None

Source code in naff/models/discord/guild.py
471
472
473
474
475
476
477
478
479
def get_owner(self) -> "models.Member":
    """
    Return the Guild owner

    Returns:
        Member object or None

    """
    return self._client.cache.get_member(self.id, self._owner_id)

fetch_channels() async

Fetch this guild's channels.

Returns:

Type Description
List[TYPE_VOICE_CHANNEL]

A list of channels in this guild

Source code in naff/models/discord/guild.py
481
482
483
484
485
486
487
488
489
490
async def fetch_channels(self) -> List["models.TYPE_VOICE_CHANNEL"]:
    """
    Fetch this guild's channels.

    Returns:
        A list of channels in this guild

    """
    data = await self._client.http.get_guild_channels(self.id)
    return [self._client.cache.place_channel_data(channel_data) for channel_data in data]

is_owner(user)

Whether the user is owner of the guild.

Parameters:

Name Type Description Default
user Snowflake_Type

The user to check

required

Returns:

Type Description
bool

True if the user is the owner of the guild, False otherwise.

Note

the user argument can be any type that meets Snowflake_Type

Source code in naff/models/discord/guild.py
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
def is_owner(self, user: Snowflake_Type) -> bool:
    """
    Whether the user is owner of the guild.

    Args:
        user: The user to check

    Returns:
        True if the user is the owner of the guild, False otherwise.

    !!! note
        the `user` argument can be any type that meets `Snowflake_Type`

    """
    return self._owner_id == to_snowflake(user)

edit_nickname(new_nickname=MISSING, reason=MISSING) async

Alias for me.edit_nickname

Parameters:

Name Type Description Default
new_nickname Absent[str]

The new nickname to apply

MISSING
reason Absent[str]

The reason for this change

MISSING

Note

Leave new_nickname empty to clean user's nickname

Source code in naff/models/discord/guild.py
508
509
510
511
512
513
514
515
516
517
518
519
520
async def edit_nickname(self, new_nickname: Absent[str] = MISSING, reason: Absent[str] = MISSING) -> None:
    """
    Alias for me.edit_nickname

    Args:
        new_nickname: The new nickname to apply
        reason: The reason for this change

    !!! note
        Leave `new_nickname` empty to clean user's nickname

    """
    await self.me.edit_nickname(new_nickname, reason=reason)

http_chunk() async

Populates all members of this guild using the REST API.

Source code in naff/models/discord/guild.py
522
523
524
525
526
527
528
529
530
531
532
533
async def http_chunk(self) -> None:
    """Populates all members of this guild using the REST API."""
    start_time = time.perf_counter()

    iterator = MemberIterator(self)
    async for member in iterator:
        self._client.cache.place_member_data(self.id, member)

    self.chunked.set()
    logger.info(
        f"Cached {iterator.total_retrieved} members for {self.id} in {time.perf_counter() - start_time:.2f} seconds"
    )

gateway_chunk(wait=True, presences=True) async

Trigger a gateway get_members event, populating this object with members.

Parameters:

Name Type Description Default
wait

Wait for chunking to be completed before continuing

True
presences

Do you need presence data for members?

True
Source code in naff/models/discord/guild.py
535
536
537
538
539
540
541
542
543
544
545
546
async def gateway_chunk(self, wait=True, presences=True) -> None:
    """
    Trigger a gateway `get_members` event, populating this object with members.

    Args:
        wait: Wait for chunking to be completed before continuing
        presences: Do you need presence data for members?
    """
    ws = self._client.get_guild_websocket(self.id)
    await ws.request_member_chunks(self.id, limit=0, presences=presences)
    if wait:
        await self.chunked.wait()

chunk() async

Populates all members of this guild using the REST API.

Source code in naff/models/discord/guild.py
548
549
550
async def chunk(self) -> None:
    """Populates all members of this guild using the REST API."""
    await self.http_chunk()

chunk_guild(wait=True, presences=True) async

Trigger a gateway get_members event, populating this object with members.

Depreciation Warning

Gateway chunking is deprecated and replaced by http chunking. Use guild.gateway_chunk if you need gateway chunking.

Parameters:

Name Type Description Default
wait

Wait for chunking to be completed before continuing

True
presences

Do you need presence data for members?

True
Source code in naff/models/discord/guild.py
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
async def chunk_guild(self, wait=True, presences=True) -> None:
    """
    Trigger a gateway `get_members` event, populating this object with members.

    !!! warning "Depreciation Warning"
        Gateway chunking is deprecated and replaced by http chunking. Use `guild.gateway_chunk` if you need gateway chunking.

    Args:
        wait: Wait for chunking to be completed before continuing
        presences: Do you need presence data for members?

    """
    warn(
        "Gateway chunking is deprecated and replaced by http chunking. Use `guild.gateway_chunk` if you need gateway chunking.",
        DeprecationWarning,
        stacklevel=2,
    )
    await self.gateway_chunk(wait=wait, presences=presences)

process_member_chunk(chunk) async

Receive and either cache or process the chunks of members from gateway.

Parameters:

Name Type Description Default
chunk dict

A member chunk from discord

required
Source code in naff/models/discord/guild.py
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
async def process_member_chunk(self, chunk: dict) -> None:
    """
    Receive and either cache or process the chunks of members from gateway.

    Args:
        chunk: A member chunk from discord

    """
    if self.chunked.is_set():
        self.chunked.clear()

    if presences := chunk.get("presences"):
        # combine the presence dict into the members dict
        for presence in presences:
            u_id = presence["user"]["id"]
            # find the user this presence is for
            member_index = next(
                (index for (index, d) in enumerate(chunk.get("members")) if d["user"]["id"] == u_id), None
            )
            del presence["user"]
            chunk["members"][member_index]["user"] = chunk["members"][member_index]["user"] | presence

    if not self._chunk_cache:
        self._chunk_cache: List = chunk.get("members")
    else:
        self._chunk_cache = self._chunk_cache + chunk.get("members")

    if chunk.get("chunk_index") != chunk.get("chunk_count") - 1:
        return logger.debug(f"Cached chunk of {len(chunk.get('members'))} members for {self.id}")
    else:
        members = self._chunk_cache
        logger.info(f"Processing {len(members)} members for {self.id}")

        s = time.monotonic()
        start_time = time.perf_counter()

        for member in members:
            self._client.cache.place_member_data(self.id, member)
            if (time.monotonic() - s) > 0.05:
                # look, i get this *could* be a thread, but because it needs to modify data in the main thread,
                # it is still blocking. So by periodically yielding to the event loop, we can avoid blocking, and still
                # process this data properly
                await asyncio.sleep(0)
                s = time.monotonic()

        total_time = time.perf_counter() - start_time
        self.chunk_cache = []
        logger.info(f"Cached members for {self.id} in {total_time:.2f} seconds")
        self.chunked.set()

fetch_audit_log(user_id=MISSING, action_type=MISSING, before=MISSING, after=MISSING, limit=100) async

Fetch section of the audit log for this guild.

Parameters:

Name Type Description Default
user_id Optional[Snowflake_Type]

The ID of the user to filter by

MISSING
action_type Optional[AuditLogEventType]

The type of action to filter by

MISSING
before Optional[Snowflake_Type]

The ID of the entry to start at

MISSING
after Optional[Snowflake_Type]

The ID of the entry to end at

MISSING
limit int

The number of entries to return

100

Returns:

Type Description
AuditLog

An AuditLog object

Source code in naff/models/discord/guild.py
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
async def fetch_audit_log(
    self,
    user_id: Optional["Snowflake_Type"] = MISSING,
    action_type: Optional["AuditLogEventType"] = MISSING,
    before: Optional["Snowflake_Type"] = MISSING,
    after: Optional["Snowflake_Type"] = MISSING,
    limit: int = 100,
) -> "AuditLog":
    """
    Fetch section of the audit log for this guild.

    Args:
        user_id: The ID of the user to filter by
        action_type: The type of action to filter by
        before: The ID of the entry to start at
        after: The ID of the entry to end at
        limit: The number of entries to return

    Returns:
        An AuditLog object

    """
    data = await self._client.http.get_audit_log(self.id, user_id, action_type, before, after, limit)
    return AuditLog.from_dict(data, self._client)

audit_log_history(user_id=MISSING, action_type=MISSING, before=MISSING, after=MISSING, limit=100)

Get an async iterator for the history of the audit log.

Parameters:

Name Type Description Default
user_id MISSING
action_type MISSING
before Optional[Snowflake_Type]

get entries before this message ID

MISSING
after Optional[Snowflake_Type]

get entries after this message ID

MISSING
limit int

The maximum number of entries to return (set to 0 for no limit)

100
Example Usage:

1
2
3
4
async for entry in guild.audit_log_history(limit=0):
    entry: "AuditLogEntry"
    if entry.changes:
        # ...
or
1
2
3
history = guild.audit_log_history(limit=250)
# Flatten the async iterator into a list
entries = await history.flatten()

Returns:

Type Description
AuditLogHistory

AuditLogHistory (AsyncIterator)

Source code in naff/models/discord/guild.py
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
def audit_log_history(
    self,
    user_id: Optional["Snowflake_Type"] = MISSING,
    action_type: Optional["AuditLogEventType"] = MISSING,
    before: Optional["Snowflake_Type"] = MISSING,
    after: Optional["Snowflake_Type"] = MISSING,
    limit: int = 100,
) -> "AuditLogHistory":
    """
    Get an async iterator for the history of the audit log.

    Args:
        user_id (:class:`Snowflake_Type`): The user ID to search for.
        action_type (:class:`AuditLogEventType`): The action type to search for.
        before: get entries before this message ID
        after: get entries after this message ID
        limit: The maximum number of entries to return (set to 0 for no limit)

    ??? Hint "Example Usage:"
        ```python
        async for entry in guild.audit_log_history(limit=0):
            entry: "AuditLogEntry"
            if entry.changes:
                # ...
        ```
        or
        ```python
        history = guild.audit_log_history(limit=250)
        # Flatten the async iterator into a list
        entries = await history.flatten()
        ```

    Returns:
        AuditLogHistory (AsyncIterator)

    """
    return AuditLogHistory(self, user_id, action_type, before, after, limit)

edit(name=MISSING, description=MISSING, verification_level=MISSING, default_message_notifications=MISSING, explicit_content_filter=MISSING, afk_channel=MISSING, afk_timeout=MISSING, system_channel=MISSING, system_channel_flags=MISSING, owner=MISSING, icon=MISSING, splash=MISSING, discovery_splash=MISSING, banner=MISSING, rules_channel=MISSING, public_updates_channel=MISSING, preferred_locale=MISSING, features=MISSING, reason=MISSING) async

Edit the guild.

Parameters:

Name Type Description Default
name Absent[Optional[str]]

The new name of the guild.

MISSING
description Absent[Optional[str]]

The new description of the guild.

MISSING
verification_level Absent[Optional[VerificationLevels]]

The new verification level for the guild.

MISSING
default_message_notifications Absent[Optional[DefaultNotificationLevels]]

The new notification level for the guild.

MISSING
explicit_content_filter Absent[Optional[ExplicitContentFilterLevels]]

The new explicit content filter level for the guild.

MISSING
afk_channel Absent[Optional[Union[GuildVoice, Snowflake_Type]]]

The voice channel that should be the new AFK channel.

MISSING
afk_timeout Absent[Optional[int]]

How many seconds does a member need to be afk before they get moved to the AFK channel. Must be either 60, 300, 900, 1800 or 3600, otherwise HTTPException will be raised.

MISSING
icon Absent[Optional[UPLOADABLE_TYPE]]

The new icon. Requires a bytes like object or a path to an image.

MISSING
owner Absent[Optional[Union[Member, Snowflake_Type]]]

The new owner of the guild. You, the bot, need to be owner for this to work.

MISSING
splash Absent[Optional[UPLOADABLE_TYPE]]

The new invite splash image. Requires a bytes like object or a path to an image.

MISSING
discovery_splash Absent[Optional[UPLOADABLE_TYPE]]

The new discovery image. Requires a bytes like object or a path to an image.

MISSING
banner Absent[Optional[UPLOADABLE_TYPE]]

The new banner image. Requires a bytes like object or a path to an image.

MISSING
system_channel Absent[Optional[Union[GuildText, Snowflake_Type]]]

The text channel where new system messages should appear. This includes boosts and welcome messages.

MISSING
system_channel_flags Absent[Union[SystemChannelFlags, int]]

The new settings for the system channel.

MISSING
rules_channel Absent[Optional[Union[GuildText, Snowflake_Type]]]

The text channel where your rules and community guidelines are displayed.

MISSING
public_updates_channel Absent[Optional[Union[GuildText, Snowflake_Type]]]

The text channel where updates from discord should appear.

MISSING
preferred_locale Absent[Optional[str]]

The new preferred locale of the guild. Must be an ISO 639 code.

MISSING
features Absent[Optional[list[str]]]

The enabled guild features

MISSING
reason Absent[Optional[str]]

An optional reason for the audit log.

MISSING
Source code in naff/models/discord/guild.py
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
async def edit(
    self,
    name: Absent[Optional[str]] = MISSING,
    description: Absent[Optional[str]] = MISSING,
    verification_level: Absent[Optional["VerificationLevels"]] = MISSING,
    default_message_notifications: Absent[Optional["DefaultNotificationLevels"]] = MISSING,
    explicit_content_filter: Absent[Optional["ExplicitContentFilterLevels"]] = MISSING,
    afk_channel: Absent[Optional[Union["models.GuildVoice", Snowflake_Type]]] = MISSING,
    afk_timeout: Absent[Optional[int]] = MISSING,
    system_channel: Absent[Optional[Union["models.GuildText", Snowflake_Type]]] = MISSING,
    system_channel_flags: Absent[Union[SystemChannelFlags, int]] = MISSING,
    owner: Absent[Optional[Union["models.Member", Snowflake_Type]]] = MISSING,
    icon: Absent[Optional[UPLOADABLE_TYPE]] = MISSING,
    splash: Absent[Optional[UPLOADABLE_TYPE]] = MISSING,
    discovery_splash: Absent[Optional[UPLOADABLE_TYPE]] = MISSING,
    banner: Absent[Optional[UPLOADABLE_TYPE]] = MISSING,
    rules_channel: Absent[Optional[Union["models.GuildText", Snowflake_Type]]] = MISSING,
    public_updates_channel: Absent[Optional[Union["models.GuildText", Snowflake_Type]]] = MISSING,
    preferred_locale: Absent[Optional[str]] = MISSING,
    # ToDo: Fill in guild features. No idea how this works - https://discord.com/developers/docs/resources/guild#guild-object-guild-features
    features: Absent[Optional[list[str]]] = MISSING,
    reason: Absent[Optional[str]] = MISSING,
) -> None:
    """
    Edit the guild.

    Args:
        name: The new name of the guild.
        description: The new description of the guild.
        verification_level: The new verification level for the guild.
        default_message_notifications: The new notification level for the guild.
        explicit_content_filter: The new explicit content filter level for the guild.
        afk_channel: The voice channel that should be the new AFK channel.
        afk_timeout: How many seconds does a member need to be afk before they get moved to the AFK channel. Must be either `60`, `300`, `900`, `1800` or `3600`, otherwise HTTPException will be raised.
        icon: The new icon. Requires a bytes like object or a path to an image.
        owner: The new owner of the guild. You, the bot, need to be owner for this to work.
        splash: The new invite splash image. Requires a bytes like object or a path to an image.
        discovery_splash: The new discovery image. Requires a bytes like object or a path to an image.
        banner: The new banner image. Requires a bytes like object or a path to an image.
        system_channel: The text channel where new system messages should appear. This includes boosts and welcome messages.
        system_channel_flags: The new settings for the system channel.
        rules_channel: The text channel where your rules and community guidelines are displayed.
        public_updates_channel: The text channel where updates from discord should appear.
        preferred_locale: The new preferred locale of the guild. Must be an ISO 639 code.
        features: The enabled guild features
        reason: An optional reason for the audit log.

    """
    await self._client.http.modify_guild(
        guild_id=self.id,
        name=name,
        description=description,
        verification_level=int(verification_level) if verification_level else MISSING,
        default_message_notifications=int(default_message_notifications)
        if default_message_notifications
        else MISSING,
        explicit_content_filter=int(explicit_content_filter) if explicit_content_filter else MISSING,
        afk_channel_id=to_snowflake(afk_channel) if afk_channel else MISSING,
        afk_timeout=afk_timeout,
        icon=to_image_data(icon) if icon else MISSING,
        owner_id=to_snowflake(owner) if owner else MISSING,
        splash=to_image_data(splash) if splash else MISSING,
        discovery_splash=to_image_data(discovery_splash) if discovery_splash else MISSING,
        banner=to_image_data(banner) if banner else MISSING,
        system_channel_id=to_snowflake(system_channel) if system_channel else MISSING,
        system_channel_flags=int(system_channel_flags) if system_channel_flags else MISSING,
        rules_channel_id=to_snowflake(rules_channel) if rules_channel else MISSING,
        public_updates_channel_id=to_snowflake(public_updates_channel) if public_updates_channel else MISSING,
        preferred_locale=preferred_locale,
        features=features,
        reason=reason,
    )

create_custom_emoji(name, imagefile, roles=MISSING, reason=MISSING) async

Create a new custom emoji for the guild.

Parameters:

Name Type Description Default
name str

Name of the emoji

required
imagefile UPLOADABLE_TYPE

The emoji image. (Supports PNG, JPEG, WebP, GIF)

required
roles Absent[List[Union[Snowflake_Type, Role]]]

Roles allowed to use this emoji.

MISSING
reason Absent[Optional[str]]

An optional reason for the audit log.

MISSING

Returns:

Type Description
CustomEmoji

The new custom emoji created.

Source code in naff/models/discord/guild.py
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
async def create_custom_emoji(
    self,
    name: str,
    imagefile: UPLOADABLE_TYPE,
    roles: Absent[List[Union[Snowflake_Type, "models.Role"]]] = MISSING,
    reason: Absent[Optional[str]] = MISSING,
) -> "models.CustomEmoji":
    """
    Create a new custom emoji for the guild.

    Args:
        name: Name of the emoji
        imagefile: The emoji image. (Supports PNG, JPEG, WebP, GIF)
        roles: Roles allowed to use this emoji.
        reason: An optional reason for the audit log.

    Returns:
        The new custom emoji created.

    """
    data_payload = {
        "name": name,
        "image": to_image_data(imagefile),
        "roles": to_snowflake_list(roles) if roles else MISSING,
    }

    emoji_data = await self._client.http.create_guild_emoji(data_payload, self.id, reason=reason)
    return self._client.cache.place_emoji_data(self.id, emoji_data)

create_guild_template(name, description=MISSING) async

Create a new guild template based on this guild.

Parameters:

Name Type Description Default
name str

The name of the template (1-100 characters)

required
description Absent[str]

The description for the template (0-120 characters)

MISSING

Returns:

Type Description
GuildTemplate

The new guild template created.

Source code in naff/models/discord/guild.py
786
787
788
789
790
791
792
793
794
795
796
797
798
799
async def create_guild_template(self, name: str, description: Absent[str] = MISSING) -> "models.GuildTemplate":
    """
    Create a new guild template based on this guild.

    Args:
        name: The name of the template (1-100 characters)
        description: The description for the template (0-120 characters)

    Returns:
        The new guild template created.

    """
    template = await self._client.http.create_guild_template(self.id, name, description)
    return GuildTemplate.from_dict(template, self._client)

fetch_guild_templates() async

Fetch all guild templates for this guild.

Returns:

Type Description
List[GuildTemplate]

A list of guild template objects.

Source code in naff/models/discord/guild.py
801
802
803
804
805
806
807
808
809
810
async def fetch_guild_templates(self) -> List["models.GuildTemplate"]:
    """
    Fetch all guild templates for this guild.

    Returns:
        A list of guild template objects.

    """
    templates = await self._client.http.get_guild_templates(self.id)
    return GuildTemplate.from_list(templates, self._client)

fetch_all_custom_emojis() async

Gets all the custom emoji present for this guild.

Returns:

Type Description
List[CustomEmoji]

A list of custom emoji objects.

Source code in naff/models/discord/guild.py
812
813
814
815
816
817
818
819
820
821
async def fetch_all_custom_emojis(self) -> List["models.CustomEmoji"]:
    """
    Gets all the custom emoji present for this guild.

    Returns:
        A list of custom emoji objects.

    """
    emojis_data = await self._client.http.get_all_guild_emoji(self.id)
    return [self._client.cache.place_emoji_data(self.id, emoji_data) for emoji_data in emojis_data]

fetch_custom_emoji(emoji_id) async

Fetches the custom emoji present for this guild, based on the emoji id.

Parameters:

Name Type Description Default
emoji_id Snowflake_Type

The target emoji to get data of.

required

Returns:

Type Description
Optional[CustomEmoji]

The custom emoji object. If the emoji is not found, returns None.

Source code in naff/models/discord/guild.py
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
async def fetch_custom_emoji(self, emoji_id: Snowflake_Type) -> Optional["models.CustomEmoji"]:
    """
    Fetches the custom emoji present for this guild, based on the emoji id.

    Args:
        emoji_id: The target emoji to get data of.

    Returns:
        The custom emoji object. If the emoji is not found, returns None.

    """
    try:
        return await self._client.cache.fetch_emoji(self.id, emoji_id)
    except NotFound:
        return None

get_custom_emoji(emoji_id)

Gets the custom emoji present for this guild, based on the emoji id.

Parameters:

Name Type Description Default
emoji_id Snowflake_Type

The target emoji to get data of.

required

Returns:

Type Description
Optional[CustomEmoji]

The custom emoji object.

Source code in naff/models/discord/guild.py
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
def get_custom_emoji(self, emoji_id: Snowflake_Type) -> Optional["models.CustomEmoji"]:
    """
    Gets the custom emoji present for this guild, based on the emoji id.

    Args:
        emoji_id: The target emoji to get data of.

    Returns:
        The custom emoji object.

    """
    emoji_id = to_snowflake(emoji_id)
    emoji = self._client.cache.get_emoji(emoji_id)
    if emoji and emoji._guild_id == self.id:
        return emoji
    return None

create_channel(channel_type, name, topic=MISSING, position=MISSING, permission_overwrites=MISSING, category=None, nsfw=False, bitrate=64000, user_limit=0, rate_limit_per_user=0, reason=MISSING) async

Create a guild channel, allows for explicit channel type setting.

Parameters:

Name Type Description Default
channel_type Union[ChannelTypes, int]

The type of channel to create

required
name str

The name of the channel

required
topic Absent[Optional[str]]

The topic of the channel

MISSING
position Absent[Optional[int]]

The position of the channel in the channel list

MISSING
permission_overwrites Absent[Union[dict, PermissionOverwrite, List[Union[dict, PermissionOverwrite]]]]

Permission overwrites to apply to the channel

MISSING
category Union[Snowflake_Type, GuildCategory]

The category this channel should be within

None
nsfw bool

Should this channel be marked nsfw

False
bitrate int

The bitrate of this channel, only for voice

64000
user_limit int

The max users that can be in this channel, only for voice

0
rate_limit_per_user int

The time users must wait between sending messages

0
reason Absent[Optional[str]]

The reason for creating this channel

MISSING

Returns:

Type Description
TYPE_GUILD_CHANNEL

The newly created channel.

Source code in naff/models/discord/guild.py
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
async def create_channel(
    self,
    channel_type: Union[ChannelTypes, int],
    name: str,
    topic: Absent[Optional[str]] = MISSING,
    position: Absent[Optional[int]] = MISSING,
    permission_overwrites: Absent[
        Union[dict, "models.PermissionOverwrite", List[Union[dict, "models.PermissionOverwrite"]]]
    ] = MISSING,
    category: Union[Snowflake_Type, "models.GuildCategory"] = None,
    nsfw: bool = False,
    bitrate: int = 64000,
    user_limit: int = 0,
    rate_limit_per_user: int = 0,
    reason: Absent[Optional[str]] = MISSING,
) -> "models.TYPE_GUILD_CHANNEL":
    """
    Create a guild channel, allows for explicit channel type setting.

    Args:
        channel_type: The type of channel to create
        name: The name of the channel
        topic: The topic of the channel
        position: The position of the channel in the channel list
        permission_overwrites: Permission overwrites to apply to the channel
        category: The category this channel should be within
        nsfw: Should this channel be marked nsfw
        bitrate: The bitrate of this channel, only for voice
        user_limit: The max users that can be in this channel, only for voice
        rate_limit_per_user: The time users must wait between sending messages
        reason: The reason for creating this channel

    Returns:
        The newly created channel.

    """
    channel_data = await self._client.http.create_guild_channel(
        self.id,
        name,
        channel_type,
        topic,
        position,
        models.process_permission_overwrites(permission_overwrites),
        to_optional_snowflake(category),
        nsfw,
        bitrate,
        user_limit,
        rate_limit_per_user,
        reason,
    )
    return self._client.cache.place_channel_data(channel_data)

create_text_channel(name, topic=MISSING, position=MISSING, permission_overwrites=MISSING, category=None, nsfw=False, rate_limit_per_user=0, reason=MISSING) async

Create a text channel in this guild.

Parameters:

Name Type Description Default
name str

The name of the channel

required
topic Absent[Optional[str]]

The topic of the channel

MISSING
position Absent[Optional[int]]

The position of the channel in the channel list

MISSING
permission_overwrites Absent[Union[dict, PermissionOverwrite, List[Union[dict, PermissionOverwrite]]]]

Permission overwrites to apply to the channel

MISSING
category Union[Snowflake_Type, GuildCategory]

The category this channel should be within

None
nsfw bool

Should this channel be marked nsfw

False
rate_limit_per_user int

The time users must wait between sending messages

0
reason Absent[Optional[str]]

The reason for creating this channel

MISSING

Returns:

Type Description
GuildText

The newly created text channel.

Source code in naff/models/discord/guild.py
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
async def create_text_channel(
    self,
    name: str,
    topic: Absent[Optional[str]] = MISSING,
    position: Absent[Optional[int]] = MISSING,
    permission_overwrites: Absent[
        Union[dict, "models.PermissionOverwrite", List[Union[dict, "models.PermissionOverwrite"]]]
    ] = MISSING,
    category: Union[Snowflake_Type, "models.GuildCategory"] = None,
    nsfw: bool = False,
    rate_limit_per_user: int = 0,
    reason: Absent[Optional[str]] = MISSING,
) -> "models.GuildText":
    """
    Create a text channel in this guild.

    Args:
        name: The name of the channel
        topic: The topic of the channel
        position: The position of the channel in the channel list
        permission_overwrites: Permission overwrites to apply to the channel
        category: The category this channel should be within
        nsfw: Should this channel be marked nsfw
        rate_limit_per_user: The time users must wait between sending messages
        reason: The reason for creating this channel

    Returns:
       The newly created text channel.

    """
    return await self.create_channel(
        channel_type=ChannelTypes.GUILD_TEXT,
        name=name,
        topic=topic,
        position=position,
        permission_overwrites=permission_overwrites,
        category=category,
        nsfw=nsfw,
        rate_limit_per_user=rate_limit_per_user,
        reason=reason,
    )

create_forum_channel(name, topic=MISSING, position=MISSING, permission_overwrites=MISSING, category=None, nsfw=False, rate_limit_per_user=0, reason=MISSING) async

Create a forum channel in this guild.

Parameters:

Name Type Description Default
name str

The name of the forum channel

required
topic Absent[Optional[str]]

The topic of the forum channel

MISSING
position Absent[Optional[int]]

The position of the forum channel in the channel list

MISSING
permission_overwrites Absent[Union[dict, PermissionOverwrite, List[Union[dict, PermissionOverwrite]]]]

Permission overwrites to apply to the forum channel

MISSING
category Union[Snowflake_Type, GuildCategory]

The category this forum channel should be within

None
nsfw bool

Should this forum be marked nsfw

False
rate_limit_per_user int

The time users must wait between sending messages

0
reason Absent[Optional[str]]

The reason for creating this channel

MISSING

Returns:

Type Description
GuildForum

The newly created forum channel.

Source code in naff/models/discord/guild.py
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
async def create_forum_channel(
    self,
    name: str,
    topic: Absent[Optional[str]] = MISSING,
    position: Absent[Optional[int]] = MISSING,
    permission_overwrites: Absent[
        Union[dict, "models.PermissionOverwrite", List[Union[dict, "models.PermissionOverwrite"]]]
    ] = MISSING,
    category: Union[Snowflake_Type, "models.GuildCategory"] = None,
    nsfw: bool = False,
    rate_limit_per_user: int = 0,
    reason: Absent[Optional[str]] = MISSING,
) -> "models.GuildForum":
    """
    Create a forum channel in this guild.

    Args:
        name: The name of the forum channel
        topic: The topic of the forum channel
        position: The position of the forum channel in the channel list
        permission_overwrites: Permission overwrites to apply to the forum channel
        category: The category this forum channel should be within
        nsfw: Should this forum be marked nsfw
        rate_limit_per_user: The time users must wait between sending messages
        reason: The reason for creating this channel

    Returns:
       The newly created forum channel.

    """
    return await self.create_channel(
        channel_type=ChannelTypes.GUILD_FORUM,
        name=name,
        topic=topic,
        position=position,
        permission_overwrites=permission_overwrites,
        category=category,
        nsfw=nsfw,
        rate_limit_per_user=rate_limit_per_user,
        reason=reason,
    )

create_news_channel(name, topic=MISSING, position=MISSING, permission_overwrites=MISSING, category=None, nsfw=False, reason=MISSING) async

Create a news channel in this guild.

Parameters:

Name Type Description Default
name str

The name of the channel

required
topic Absent[Optional[str]]

The topic of the channel

MISSING
position Absent[Optional[int]]

The position of the channel in the channel list

MISSING
permission_overwrites Absent[Union[dict, PermissionOverwrite, List[Union[dict, PermissionOverwrite]]]]

Permission overwrites to apply to the channel

MISSING
category Union[Snowflake_Type, GuildCategory]

The category this channel should be within

None
nsfw bool

Should this channel be marked nsfw

False
reason Absent[Optional[str]]

The reason for creating this channel

MISSING

Returns:

Type Description
GuildNews

The newly created news channel.

Source code in naff/models/discord/guild.py
 992
 993
 994
 995
 996
 997
 998
 999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
async def create_news_channel(
    self,
    name: str,
    topic: Absent[Optional[str]] = MISSING,
    position: Absent[Optional[int]] = MISSING,
    permission_overwrites: Absent[
        Union[dict, "models.PermissionOverwrite", List[Union[dict, "models.PermissionOverwrite"]]]
    ] = MISSING,
    category: Union[Snowflake_Type, "models.GuildCategory"] = None,
    nsfw: bool = False,
    reason: Absent[Optional[str]] = MISSING,
) -> "models.GuildNews":
    """
    Create a news channel in this guild.

    Args:
        name: The name of the channel
        topic: The topic of the channel
        position: The position of the channel in the channel list
        permission_overwrites: Permission overwrites to apply to the channel
        category: The category this channel should be within
        nsfw: Should this channel be marked nsfw
        reason: The reason for creating this channel

    Returns:
       The newly created news channel.

    """
    return await self.create_channel(
        channel_type=ChannelTypes.GUILD_NEWS,
        name=name,
        topic=topic,
        position=position,
        permission_overwrites=permission_overwrites,
        category=category,
        nsfw=nsfw,
        reason=reason,
    )

create_voice_channel(name, topic=MISSING, position=MISSING, permission_overwrites=MISSING, category=None, nsfw=False, bitrate=64000, user_limit=0, reason=MISSING) async

Create a guild voice channel.

Parameters:

Name Type Description Default
name str

The name of the channel

required
topic Absent[Optional[str]]

The topic of the channel

MISSING
position Absent[Optional[int]]

The position of the channel in the channel list

MISSING
permission_overwrites Absent[Union[dict, PermissionOverwrite, List[Union[dict, PermissionOverwrite]]]]

Permission overwrites to apply to the channel

MISSING
category Union[Snowflake_Type, GuildCategory]

The category this channel should be within

None
nsfw bool

Should this channel be marked nsfw

False
bitrate int

The bitrate of this channel, only for voice

64000
user_limit int

The max users that can be in this channel, only for voice

0
reason Absent[Optional[str]]

The reason for creating this channel

MISSING

Returns:

Type Description
GuildVoice

The newly created voice channel.

Source code in naff/models/discord/guild.py
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
async def create_voice_channel(
    self,
    name: str,
    topic: Absent[Optional[str]] = MISSING,
    position: Absent[Optional[int]] = MISSING,
    permission_overwrites: Absent[
        Union[dict, "models.PermissionOverwrite", List[Union[dict, "models.PermissionOverwrite"]]]
    ] = MISSING,
    category: Union[Snowflake_Type, "models.GuildCategory"] = None,
    nsfw: bool = False,
    bitrate: int = 64000,
    user_limit: int = 0,
    reason: Absent[Optional[str]] = MISSING,
) -> "models.GuildVoice":
    """
    Create a guild voice channel.

    Args:
        name: The name of the channel
        topic: The topic of the channel
        position: The position of the channel in the channel list
        permission_overwrites: Permission overwrites to apply to the channel
        category: The category this channel should be within
        nsfw: Should this channel be marked nsfw
        bitrate: The bitrate of this channel, only for voice
        user_limit: The max users that can be in this channel, only for voice
        reason: The reason for creating this channel

    Returns:
       The newly created voice channel.

    """
    return await self.create_channel(
        channel_type=ChannelTypes.GUILD_VOICE,
        name=name,
        topic=topic,
        position=position,
        permission_overwrites=permission_overwrites,
        category=category,
        nsfw=nsfw,
        bitrate=bitrate,
        user_limit=user_limit,
        reason=reason,
    )

create_stage_channel(name, topic=MISSING, position=MISSING, permission_overwrites=MISSING, category=MISSING, bitrate=64000, user_limit=0, reason=MISSING) async

Create a guild stage channel.

Parameters:

Name Type Description Default
name str

The name of the channel

required
topic Absent[Optional[str]]

The topic of the channel

MISSING
position Absent[Optional[int]]

The position of the channel in the channel list

MISSING
permission_overwrites Absent[Union[dict, PermissionOverwrite, List[Union[dict, PermissionOverwrite]]]]

Permission overwrites to apply to the channel

MISSING
category Absent[Union[Snowflake_Type, GuildCategory]]

The category this channel should be within

MISSING
bitrate int

The bitrate of this channel, only for voice

64000
user_limit int

The max users that can be in this channel, only for voice

0
reason Absent[Optional[str]]

The reason for creating this channel

MISSING

Returns:

Type Description
GuildStageVoice

The newly created stage channel.

Source code in naff/models/discord/guild.py
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
async def create_stage_channel(
    self,
    name: str,
    topic: Absent[Optional[str]] = MISSING,
    position: Absent[Optional[int]] = MISSING,
    permission_overwrites: Absent[
        Union[dict, "models.PermissionOverwrite", List[Union[dict, "models.PermissionOverwrite"]]]
    ] = MISSING,
    category: Absent[Union[Snowflake_Type, "models.GuildCategory"]] = MISSING,
    bitrate: int = 64000,
    user_limit: int = 0,
    reason: Absent[Optional[str]] = MISSING,
) -> "models.GuildStageVoice":
    """
    Create a guild stage channel.

    Args:
        name: The name of the channel
        topic: The topic of the channel
        position: The position of the channel in the channel list
        permission_overwrites: Permission overwrites to apply to the channel
        category: The category this channel should be within
        bitrate: The bitrate of this channel, only for voice
        user_limit: The max users that can be in this channel, only for voice
        reason: The reason for creating this channel

    Returns:
        The newly created stage channel.

    """
    return await self.create_channel(
        channel_type=ChannelTypes.GUILD_STAGE_VOICE,
        name=name,
        topic=topic,
        position=position,
        permission_overwrites=permission_overwrites,
        category=category,
        bitrate=bitrate,
        user_limit=user_limit,
        reason=reason,
    )

create_category(name, position=MISSING, permission_overwrites=MISSING, reason=MISSING) async

Create a category within this guild.

Parameters:

Name Type Description Default
name str

The name of the channel

required
position Absent[Optional[int]]

The position of the channel in the channel list

MISSING
permission_overwrites Absent[Union[dict, PermissionOverwrite, List[Union[dict, PermissionOverwrite]]]]

Permission overwrites to apply to the channel

MISSING
reason Absent[Optional[str]]

The reason for creating this channel

MISSING

Returns:

Type Description
GuildCategory

The newly created category.

Source code in naff/models/discord/guild.py
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
async def create_category(
    self,
    name: str,
    position: Absent[Optional[int]] = MISSING,
    permission_overwrites: Absent[
        Union[dict, "models.PermissionOverwrite", List[Union[dict, "models.PermissionOverwrite"]]]
    ] = MISSING,
    reason: Absent[Optional[str]] = MISSING,
) -> "models.GuildCategory":
    """
    Create a category within this guild.

    Args:
        name: The name of the channel
        position: The position of the channel in the channel list
        permission_overwrites: Permission overwrites to apply to the channel
        reason: The reason for creating this channel

    Returns:
        The newly created category.

    """
    return await self.create_channel(
        channel_type=ChannelTypes.GUILD_CATEGORY,
        name=name,
        position=position,
        permission_overwrites=permission_overwrites,
        reason=reason,
    )

delete_channel(channel, reason=None) async

Delete the given channel, can handle either a snowflake or channel object.

This is effectively just an alias for channel.delete()

Parameters:

Name Type Description Default
channel Union[TYPE_GUILD_CHANNEL, Snowflake_Type]

The channel to be deleted

required
reason str

The reason for this deletion

None
Source code in naff/models/discord/guild.py
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
async def delete_channel(
    self, channel: Union["models.TYPE_GUILD_CHANNEL", Snowflake_Type], reason: str = None
) -> None:
    """
    Delete the given channel, can handle either a snowflake or channel object.

    This is effectively just an alias for `channel.delete()`

    Args:
        channel: The channel to be deleted
        reason: The reason for this deletion

    """
    if isinstance(channel, (str, int)):
        channel = await self._client.fetch_channel(channel)

    if not channel:
        raise ValueError("Unable to find requested channel")

    if channel.id not in self._channel_ids:
        raise ValueError("This guild does not hold the requested channel")

    await channel.delete(reason)

list_scheduled_events(with_user_count=False) async

List all scheduled events in this guild.

Returns:

Type Description
List[ScheduledEvent]

A list of scheduled events.

Source code in naff/models/discord/guild.py
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
async def list_scheduled_events(self, with_user_count: bool = False) -> List["models.ScheduledEvent"]:
    """
    List all scheduled events in this guild.

    Returns:
        A list of scheduled events.

    """
    scheduled_events_data = await self._client.http.list_schedules_events(self.id, with_user_count)
    return models.ScheduledEvent.from_list(scheduled_events_data, self._client)

fetch_scheduled_event(scheduled_event_id, with_user_count=False) async

Get a scheduled event by id.

Parameters:

Name Type Description Default
scheduled_event_id Snowflake_Type

The id of the scheduled event.

required
with_user_count bool

Whether to include the user count in the response.

False

Returns:

Type Description
Optional[ScheduledEvent]

The scheduled event. If the event does not exist, returns None.

Source code in naff/models/discord/guild.py
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
async def fetch_scheduled_event(
    self, scheduled_event_id: Snowflake_Type, with_user_count: bool = False
) -> Optional["models.ScheduledEvent"]:
    """
    Get a scheduled event by id.

    Args:
        scheduled_event_id: The id of the scheduled event.
        with_user_count: Whether to include the user count in the response.

    Returns:
        The scheduled event. If the event does not exist, returns None.

    """
    try:
        scheduled_event_data = await self._client.http.get_scheduled_event(
            self.id, scheduled_event_id, with_user_count
        )
    except NotFound:
        return None
    return models.ScheduledEvent.from_dict(scheduled_event_data, self._client)

create_scheduled_event(name, event_type, start_time, end_time=MISSING, description=MISSING, channel_id=MISSING, external_location=MISSING, entity_metadata=None, privacy_level=ScheduledEventPrivacyLevel.GUILD_ONLY, cover_image=MISSING, reason=MISSING) async

Create a scheduled guild event.

Parameters:

Name Type Description Default
name str

event name

required
event_type ScheduledEventType

event type

required
start_time Timestamp

Timestamp object

required
end_time Absent[Optional[Timestamp]]

Timestamp object

MISSING
description Absent[Optional[str]]

event description

MISSING
channel_id Absent[Optional[Snowflake_Type]]

channel id

MISSING
external_location Absent[Optional[str]]

event external location (For external events)

MISSING
entity_metadata Optional[dict]

event metadata (additional data for the event)

None
privacy_level ScheduledEventPrivacyLevel

event privacy level

ScheduledEventPrivacyLevel.GUILD_ONLY
cover_image Absent[UPLOADABLE_TYPE]

the cover image of the scheduled event

MISSING
reason Absent[Optional[str]]

reason for creating this scheduled event

MISSING

Returns:

Type Description
ScheduledEvent

The newly created ScheduledEvent object

Note

For external events, external_location is required For voice/stage events, channel_id is required

Note

entity_metadata is the backend dictionary for fluff fields. Where possible, we plan to expose these fields directly. The full list of supported fields is https://discord.com/developers/docs/resources/guild-scheduled-event#guild-scheduled-event-object-guild-scheduled-event-entity-metadata Example: entity_metadata=dict(location="cool place")

Source code in naff/models/discord/guild.py
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
async def create_scheduled_event(
    self,
    name: str,
    event_type: ScheduledEventType,
    start_time: "models.Timestamp",
    end_time: Absent[Optional["models.Timestamp"]] = MISSING,
    description: Absent[Optional[str]] = MISSING,
    channel_id: Absent[Optional[Snowflake_Type]] = MISSING,
    external_location: Absent[Optional[str]] = MISSING,
    entity_metadata: Optional[dict] = None,
    privacy_level: ScheduledEventPrivacyLevel = ScheduledEventPrivacyLevel.GUILD_ONLY,
    cover_image: Absent[UPLOADABLE_TYPE] = MISSING,
    reason: Absent[Optional[str]] = MISSING,
) -> "models.ScheduledEvent":
    """
    Create a scheduled guild event.

    Args:
        name: event name
        event_type: event type
        start_time: `Timestamp` object
        end_time: `Timestamp` object
        description: event description
        channel_id: channel id
        external_location: event external location (For external events)
        entity_metadata: event metadata (additional data for the event)
        privacy_level: event privacy level
        cover_image: the cover image of the scheduled event
        reason: reason for creating this scheduled event

    Returns:
        The newly created ScheduledEvent object

    !!! note
        For external events, external_location is required
        For voice/stage events, channel_id is required

    ??? note
        entity_metadata is the backend dictionary for fluff fields. Where possible, we plan to expose these fields directly.
        The full list of supported fields is https://discord.com/developers/docs/resources/guild-scheduled-event#guild-scheduled-event-object-guild-scheduled-event-entity-metadata
        Example: `entity_metadata=dict(location="cool place")`

    """
    if external_location is not MISSING:
        entity_metadata = {"location": external_location}

    if event_type == ScheduledEventType.EXTERNAL:
        if external_location == MISSING:
            raise EventLocationNotProvided("Location is required for external events")

    payload = {
        "name": name,
        "entity_type": event_type,
        "scheduled_start_time": start_time.isoformat(),
        "scheduled_end_time": end_time.isoformat() if end_time is not MISSING else end_time,
        "description": description,
        "channel_id": channel_id,
        "entity_metadata": entity_metadata,
        "privacy_level": privacy_level,
        "image": to_image_data(cover_image) if cover_image else MISSING,
    }

    scheduled_event_data = await self._client.http.create_scheduled_event(self.id, payload, reason)
    return models.ScheduledEvent.from_dict(scheduled_event_data, self._client)

create_custom_sticker(name, imagefile, description=MISSING, tags=MISSING, reason=MISSING) async

Creates a custom sticker for a guild.

Parameters:

Name Type Description Default
name str

The name of the sticker (2-30 characters)

required
imagefile UPLOADABLE_TYPE

The sticker file to upload, must be a PNG, APNG, or Lottie JSON file (max 500 KB)

required
description Absent[Optional[str]]

The description of the sticker (empty or 2-100 characters)

MISSING
tags Absent[Optional[str]]

Autocomplete/suggestion tags for the sticker (max 200 characters)

MISSING
reason Absent[Optional[str]]

Reason for creating the sticker

MISSING

Returns:

Type Description
Sticker

New Sticker instance

Source code in naff/models/discord/guild.py
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
async def create_custom_sticker(
    self,
    name: str,
    imagefile: UPLOADABLE_TYPE,
    description: Absent[Optional[str]] = MISSING,
    tags: Absent[Optional[str]] = MISSING,
    reason: Absent[Optional[str]] = MISSING,
) -> "models.Sticker":
    """
    Creates a custom sticker for a guild.

    Args:
        name: The name of the sticker (2-30 characters)
        imagefile: The sticker file to upload, must be a PNG, APNG, or Lottie JSON file (max 500 KB)
        description: The description of the sticker (empty or 2-100 characters)
        tags: Autocomplete/suggestion tags for the sticker (max 200 characters)
        reason: Reason for creating the sticker

    Returns:
        New Sticker instance

    """
    payload = {"name": name}

    if description:
        payload["description"] = description

    if tags:
        payload["tags"] = tags

    sticker_data = await self._client.http.create_guild_sticker(payload, self.id, reason)
    return models.Sticker.from_dict(sticker_data, self._client)

fetch_all_custom_stickers() async

Fetches all custom stickers for a guild.

Returns:

Type Description
List[Sticker]

List of Sticker objects

Source code in naff/models/discord/guild.py
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
async def fetch_all_custom_stickers(self) -> List["models.Sticker"]:
    """
    Fetches all custom stickers for a guild.

    Returns:
        List of Sticker objects

    """
    stickers_data = await self._client.http.list_guild_stickers(self.id)
    return models.Sticker.from_list(stickers_data, self._client)

fetch_custom_sticker(sticker_id) async

Fetches a specific custom sticker for a guild.

Parameters:

Name Type Description Default
sticker_id Snowflake_Type

ID of sticker to get

required

Returns:

Type Description
Optional[Sticker]

The custom sticker object. If the sticker does not exist, returns None.

Source code in naff/models/discord/guild.py
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
async def fetch_custom_sticker(self, sticker_id: Snowflake_Type) -> Optional["models.Sticker"]:
    """
    Fetches a specific custom sticker for a guild.

    Args:
        sticker_id: ID of sticker to get

    Returns:
        The custom sticker object. If the sticker does not exist, returns None.

    """
    try:
        sticker_data = await self._client.http.get_guild_sticker(self.id, to_snowflake(sticker_id))
    except NotFound:
        return None
    return models.Sticker.from_dict(sticker_data, self._client)

fetch_active_threads() async

Fetches all active threads in the guild, including public and private threads. Threads are ordered by their id, in descending order.

Returns:

Type Description
ThreadList

List of active threads and thread member object for each returned thread the bot user has joined.

Source code in naff/models/discord/guild.py
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
async def fetch_active_threads(self) -> "models.ThreadList":
    """
    Fetches all active threads in the guild, including public and private threads. Threads are ordered by their id, in descending order.

    Returns:
        List of active threads and thread member object for each returned thread the bot user has joined.

    """
    threads_data = await self._client.http.list_active_threads(self.id)
    return models.ThreadList.from_dict(threads_data, self._client)

fetch_role(role_id) async

Fetch the specified role by ID.

Parameters:

Name Type Description Default
role_id Snowflake_Type

The ID of the role to get

required

Returns:

Type Description
Optional[Role]

The role object. If the role does not exist, returns None.

Source code in naff/models/discord/guild.py
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
async def fetch_role(self, role_id: Snowflake_Type) -> Optional["models.Role"]:
    """
    Fetch the specified role by ID.

    Args:
        role_id: The ID of the role to get

    Returns:
        The role object. If the role does not exist, returns None.

    """
    try:
        return await self._client.cache.fetch_role(self.id, role_id)
    except NotFound:
        return None

get_role(role_id)

Get the specified role by ID.

Parameters:

Name Type Description Default
role_id Snowflake_Type

The ID of the role to get

required

Returns:

Type Description
Optional[Role]

A role object or None if the role is not found.

Source code in naff/models/discord/guild.py
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
def get_role(self, role_id: Snowflake_Type) -> Optional["models.Role"]:
    """
    Get the specified role by ID.

    Args:
        role_id: The ID of the role to get

    Returns:
        A role object or None if the role is not found.

    """
    role_id = to_snowflake(role_id)
    if role_id in self._role_ids:
        return self._client.cache.get_role(role_id)
    return None

create_role(name=MISSING, permissions=MISSING, colour=MISSING, color=MISSING, hoist=False, mentionable=False, icon=MISSING, reason=MISSING) async

Create a new role for the guild. You must have the manage roles permission.

Parameters:

Name Type Description Default
name Absent[Optional[str]]

The name the role should have. Default: new role

MISSING
permissions Absent[Optional[Permissions]]

The permissions the role should have. Default: @everyone permissions

MISSING
colour Absent[Optional[Union[Color, int]]]

The colour of the role. Can be either Color or an RGB integer. Default: BrandColors.BLACK

MISSING
color Absent[Optional[Union[Color, int]]]

Alias for colour

MISSING
icon Absent[Optional[UPLOADABLE_TYPE]]

Can be either a bytes like object or a path to an image, or a unicode emoji which is supported by discord.

MISSING
hoist Optional[bool]

Whether the role is shown separately in the members list. Default: False

False
mentionable Optional[bool]

Whether the role can be mentioned. Default: False

False
reason Absent[Optional[str]]

An optional reason for the audit log.

MISSING

Returns:

Type Description
Role

A role object or None if the role is not found.

Source code in naff/models/discord/guild.py
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
async def create_role(
    self,
    name: Absent[Optional[str]] = MISSING,
    permissions: Absent[Optional[Permissions]] = MISSING,
    colour: Absent[Optional[Union["models.Color", int]]] = MISSING,
    color: Absent[Optional[Union["models.Color", int]]] = MISSING,
    hoist: Optional[bool] = False,
    mentionable: Optional[bool] = False,
    icon: Absent[Optional[UPLOADABLE_TYPE]] = MISSING,
    reason: Absent[Optional[str]] = MISSING,
) -> "models.Role":
    """
    Create a new role for the guild. You must have the `manage roles` permission.

    Args:
        name: The name the role should have. `Default: new role`
        permissions: The permissions the role should have. `Default: @everyone permissions`
        colour: The colour of the role. Can be either `Color` or an RGB integer. `Default: BrandColors.BLACK`
        color: Alias for `colour`
        icon: Can be either a bytes like object or a path to an image, or a unicode emoji which is supported by discord.
        hoist: Whether the role is shown separately in the members list. `Default: False`
        mentionable: Whether the role can be mentioned. `Default: False`
        reason: An optional reason for the audit log.

    Returns:
        A role object or None if the role is not found.

    """
    payload = {}

    if name:
        payload.update({"name": name})

    if permissions:
        payload.update({"permissions": str(int(permissions))})

    colour = colour or color
    if colour:
        payload.update({"color": colour.value})

    if hoist:
        payload.update({"hoist": True})

    if mentionable:
        payload.update({"mentionable": True})

    if icon:
        # test if the icon is probably a unicode emoji (str and len() == 1) or a path / bytes obj
        if isinstance(icon, str) and len(icon) == 1:
            payload.update({"unicode_emoji": icon})

        else:
            payload.update({"icon": to_image_data(icon)})

    result = await self._client.http.create_guild_role(guild_id=self.id, payload=payload, reason=reason)
    return self._client.cache.place_role_data(guild_id=self.id, data=[result])[to_snowflake(result["id"])]

get_channel(channel_id)

Returns a channel with the given channel_id.

Parameters:

Name Type Description Default
channel_id Snowflake_Type

The ID of the channel to get

required

Returns:

Type Description
Optional[TYPE_GUILD_CHANNEL]

Channel object if found, otherwise None

Source code in naff/models/discord/guild.py
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
def get_channel(self, channel_id: Snowflake_Type) -> Optional["models.TYPE_GUILD_CHANNEL"]:
    """
    Returns a channel with the given `channel_id`.

    Args:
        channel_id: The ID of the channel to get

    Returns:
        Channel object if found, otherwise None

    """
    channel_id = to_snowflake(channel_id)
    if channel_id in self._channel_ids:
        # theoretically, this could get any channel the client can see,
        # but to make it less confusing to new programmers,
        # i intentionally check that the guild contains the channel first
        return self._client.cache.get_channel(channel_id)
    return None

fetch_channel(channel_id) async

Returns a channel with the given channel_id from the API.

Parameters:

Name Type Description Default
channel_id Snowflake_Type

The ID of the channel to get

required

Returns:

Type Description
Optional[TYPE_GUILD_CHANNEL]

The channel object. If the channel does not exist, returns None.

Source code in naff/models/discord/guild.py
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
async def fetch_channel(self, channel_id: Snowflake_Type) -> Optional["models.TYPE_GUILD_CHANNEL"]:
    """
    Returns a channel with the given `channel_id` from the API.

    Args:
        channel_id: The ID of the channel to get

    Returns:
        The channel object. If the channel does not exist, returns None.

    """
    channel_id = to_snowflake(channel_id)
    if channel_id in self._channel_ids or not self._client.gateway_started:
        # The latter check here is to see if the bot is running with the gateway.
        # If not, then we need to check the API since only the gateway
        # populates the channel IDs

        # theoretically, this could get any channel the client can see,
        # but to make it less confusing to new programmers,
        # i intentionally check that the guild contains the channel first
        try:
            channel = await self._client.fetch_channel(channel_id)
            if channel._guild_id == self.id:
                return channel
        except (NotFound, AttributeError):
            return None

    return None

get_thread(thread_id)

Returns a Thread with the given thread_id.

Parameters:

Name Type Description Default
thread_id Snowflake_Type

The ID of the thread to get

required

Returns:

Type Description
Optional[TYPE_THREAD_CHANNEL]

Thread object if found, otherwise None

Source code in naff/models/discord/guild.py
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
def get_thread(self, thread_id: Snowflake_Type) -> Optional["models.TYPE_THREAD_CHANNEL"]:
    """
    Returns a Thread with the given `thread_id`.

    Args:
        thread_id: The ID of the thread to get

    Returns:
        Thread object if found, otherwise None

    """
    thread_id = to_snowflake(thread_id)
    if thread_id in self._thread_ids:
        return self._client.cache.get_channel(thread_id)
    return None

fetch_thread(thread_id) async

Returns a Thread with the given thread_id from the API.

Parameters:

Name Type Description Default
thread_id Snowflake_Type

The ID of the thread to get

required

Returns:

Type Description
Optional[TYPE_THREAD_CHANNEL]

Thread object if found, otherwise None

Source code in naff/models/discord/guild.py
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
async def fetch_thread(self, thread_id: Snowflake_Type) -> Optional["models.TYPE_THREAD_CHANNEL"]:
    """
    Returns a Thread with the given `thread_id` from the API.

    Args:
        thread_id: The ID of the thread to get

    Returns:
        Thread object if found, otherwise None

    """
    thread_id = to_snowflake(thread_id)
    if thread_id in self._thread_ids:
        try:
            return await self._client.fetch_channel(thread_id)
        except NotFound:
            return None
    return None

prune_members(days=7, roles=None, compute_prune_count=True, reason=MISSING) async

Begin a guild prune. Removes members from the guild who who have not interacted for the last days days. By default, members with roles are excluded from pruning, to include them, pass their role (or role id) in roles Requires kick members permission.

Parameters:

Name Type Description Default
days int

number of days to prune (1-30)

7
roles Optional[List[Snowflake_Type]]

list of roles to include in the prune

None
compute_prune_count bool

Whether the number of members pruned should be calculated (disable this for large guilds)

True
reason Absent[str]

The reason for this prune

MISSING

Returns:

Type Description
Optional[int]

The total number of members pruned, if compute_prune_count is set to True, otherwise None

Source code in naff/models/discord/guild.py
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
async def prune_members(
    self,
    days: int = 7,
    roles: Optional[List[Snowflake_Type]] = None,
    compute_prune_count: bool = True,
    reason: Absent[str] = MISSING,
) -> Optional[int]:
    """
    Begin a guild prune. Removes members from the guild who who have not interacted for the last `days` days. By default, members with roles are excluded from pruning, to include them, pass their role (or role id) in `roles` Requires `kick members` permission.

    Args:
        days: number of days to prune (1-30)
        roles: list of roles to include in the prune
        compute_prune_count: Whether the number of members pruned should be calculated (disable this for large guilds)
        reason: The reason for this prune

    Returns:
        The total number of members pruned, if `compute_prune_count` is set to True, otherwise None

    """
    if roles:
        roles = [str(to_snowflake(r)) for r in roles]

    resp = await self._client.http.begin_guild_prune(
        self.id, days, include_roles=roles, compute_prune_count=compute_prune_count, reason=reason
    )
    return resp["pruned"]

estimate_prune_members(days=7, roles=MISSING) async

Calculate how many members would be pruned, should guild.prune_members be used. By default, members with roles are excluded from pruning, to include them, pass their role (or role id) in roles.

Parameters:

Name Type Description Default
days int

number of days to prune (1-30)

7
roles List[Union[Snowflake_Type, Role]]

list of roles to include in the prune

MISSING

Returns:

Type Description
int

Total number of members that would be pruned

Source code in naff/models/discord/guild.py
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
async def estimate_prune_members(
    self, days: int = 7, roles: List[Union[Snowflake_Type, "models.Role"]] = MISSING
) -> int:
    """
    Calculate how many members would be pruned, should `guild.prune_members` be used. By default, members with roles are excluded from pruning, to include them, pass their role (or role id) in `roles`.

    Args:
        days: number of days to prune (1-30)
        roles: list of roles to include in the prune

    Returns:
        Total number of members that would be pruned

    """
    if roles is not MISSING:
        roles = [r.id if isinstance(r, models.Role) else r for r in roles]
    else:
        roles = []

    resp = await self._client.http.get_guild_prune_count(self.id, days=days, include_roles=roles)
    return resp["pruned"]

leave() async

Leave this guild.

Source code in naff/models/discord/guild.py
1564
1565
1566
async def leave(self) -> None:
    """Leave this guild."""
    await self._client.http.leave_guild(self.id)

delete() async

Delete the guild.

Note

You must own this guild to do this.

Source code in naff/models/discord/guild.py
1568
1569
1570
1571
1572
1573
1574
1575
1576
async def delete(self) -> None:
    """
    Delete the guild.

    !!! note
        You must own this guild to do this.

    """
    await self._client.http.delete_guild(self.id)

kick(user, reason=MISSING) async

Kick a user from the guild.

Note

You must have the kick members permission

Parameters:

Name Type Description Default
user Union[User, Member, Snowflake_Type]

The user to kick

required
reason Absent[str]

The reason for the kick

MISSING
Source code in naff/models/discord/guild.py
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
async def kick(
    self, user: Union["models.User", "models.Member", Snowflake_Type], reason: Absent[str] = MISSING
) -> None:
    """
    Kick a user from the guild.

    !!! note
        You must have the `kick members` permission

    Args:
        user: The user to kick
        reason: The reason for the kick

    """
    await self._client.http.remove_guild_member(self.id, to_snowflake(user), reason=reason)

ban(user, delete_message_days=MISSING, delete_message_seconds=0, reason=MISSING) async

Ban a user from the guild.

Note

You must have the ban members permission

Parameters:

Name Type Description Default
user Union[User, Member, Snowflake_Type]

The user to ban

required
delete_message_days Absent[int]

(deprecated) How many days worth of messages to remove

MISSING
delete_message_seconds int

How many seconds worth of messages to remove

0
reason Absent[str]

The reason for the ban

MISSING
Source code in naff/models/discord/guild.py
1594
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
async def ban(
    self,
    user: Union["models.User", "models.Member", Snowflake_Type],
    delete_message_days: Absent[int] = MISSING,
    delete_message_seconds: int = 0,
    reason: Absent[str] = MISSING,
) -> None:
    """
    Ban a user from the guild.

    !!! note
        You must have the `ban members` permission

    Args:
        user: The user to ban
        delete_message_days: (deprecated) How many days worth of messages to remove
        delete_message_seconds: How many seconds worth of messages to remove
        reason: The reason for the ban

    """
    if delete_message_days is not MISSING:
        warn("delete_message_days is deprecated and will be removed in a future update", DeprecationWarning)
        delete_message_seconds = delete_message_days * 3600
    await self._client.http.create_guild_ban(self.id, to_snowflake(user), delete_message_seconds, reason=reason)

fetch_ban(user) async

Fetches the ban information for the specified user in the guild. You must have the ban members permission.

Parameters:

Name Type Description Default
user Union[User, Member, Snowflake_Type]

The user to look up.

required

Returns:

Type Description
Optional[GuildBan]

The ban information. If the user is not banned, returns None.

Source code in naff/models/discord/guild.py
1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
1631
1632
1633
1634
async def fetch_ban(self, user: Union["models.User", "models.Member", Snowflake_Type]) -> Optional[GuildBan]:
    """
    Fetches the ban information for the specified user in the guild. You must have the `ban members` permission.

    Args:
        user: The user to look up.

    Returns:
        The ban information. If the user is not banned, returns None.

    """
    try:
        ban_info = await self._client.http.get_guild_ban(self.id, to_snowflake(user))
    except NotFound:
        return None
    return GuildBan(reason=ban_info["reason"], user=self._client.cache.place_user_data(ban_info["user"]))

fetch_bans(before=MISSING, after=MISSING, limit=1000) async

Fetches bans for the guild. You must have the ban members permission.

Parameters:

Name Type Description Default
before Optional[Snowflake_Type]

consider only users before given user id

MISSING
after Optional[Snowflake_Type]

consider only users after given user id

MISSING
limit int

number of users to return (up to maximum 1000)

1000

Returns:

Type Description
list[GuildBan]

A list containing bans and information about them.

Source code in naff/models/discord/guild.py
1636
1637
1638
1639
1640
1641
1642
1643
1644
1645
1646
1647
1648
1649
1650
1651
1652
1653
1654
1655
1656
1657
1658
async def fetch_bans(
    self,
    before: Optional["Snowflake_Type"] = MISSING,
    after: Optional["Snowflake_Type"] = MISSING,
    limit: int = 1000,
) -> list[GuildBan]:
    """
    Fetches bans for the guild. You must have the `ban members` permission.

    Args:
        before: consider only users before given user id
        after: consider only users after given user id
        limit: number of users to return (up to maximum 1000)

    Returns:
        A list containing bans and information about them.

    """
    ban_infos = await self._client.http.get_guild_bans(self.id, before=before, after=after, limit=limit)
    return [
        GuildBan(reason=ban_info["reason"], user=self._client.cache.place_user_data(ban_info["user"]))
        for ban_info in ban_infos
    ]

create_auto_moderation_rule(name, *, trigger, actions, exempt_roles=MISSING, exempt_channels=MISSING, enabled=True, event_type=AutoModEvent.MESSAGE_SEND) async

Create an auto-moderation rule in this guild.

Parameters:

Name Type Description Default
name str

The name of the rule

required
trigger BaseTrigger

The trigger for this rule

required
actions list[BaseAction]

A list of actions to take upon triggering

required
exempt_roles list[Snowflake_Type]

Roles that ignore this rule

MISSING
exempt_channels list[Snowflake_Type]

Channels that ignore this role

MISSING
enabled bool

Is this rule enabled?

True
event_type AutoModEvent

The type of event that triggers this rule

AutoModEvent.MESSAGE_SEND

Returns:

Type Description
AutoModRule

The created rule

Source code in naff/models/discord/guild.py
1660
1661
1662
1663
1664
1665
1666
1667
1668
1669
1670
1671
1672
1673
1674
1675
1676
1677
1678
1679
1680
1681
1682
1683
1684
1685
1686
1687
1688
1689
1690
1691
1692
1693
1694
1695
1696
1697
async def create_auto_moderation_rule(
    self,
    name: str,
    *,
    trigger: BaseTrigger,
    actions: list[BaseAction],
    exempt_roles: list["Snowflake_Type"] = MISSING,
    exempt_channels: list["Snowflake_Type"] = MISSING,
    enabled: bool = True,
    event_type: AutoModEvent = AutoModEvent.MESSAGE_SEND,
) -> AutoModRule:
    """
    Create an auto-moderation rule in this guild.

    Args:
        name: The name of the rule
        trigger: The trigger for this rule
        actions: A list of actions to take upon triggering
        exempt_roles: Roles that ignore this rule
        exempt_channels: Channels that ignore this role
        enabled: Is this rule enabled?
        event_type: The type of event that triggers this rule

    Returns:
        The created rule
    """
    rule = AutoModRule(
        name=name,
        enabled=enabled,
        actions=actions,
        event_type=event_type,
        trigger=trigger,
        exempt_channels=exempt_channels if exempt_roles is not MISSING else [],
        exempt_roles=exempt_roles if exempt_roles is not MISSING else [],
        client=self._client,
    )
    data = await self._client.http.create_auto_moderation_rule(self.id, rule.to_dict())
    return AutoModRule.from_dict(data, self._client)

fetch_auto_moderation_rules() async

Get this guild's auto moderation rules.

Returns:

Type Description
List[AutoModRule]

A list of auto moderation rules

Source code in naff/models/discord/guild.py
1699
1700
1701
1702
1703
1704
1705
1706
1707
async def fetch_auto_moderation_rules(self) -> List[AutoModRule]:
    """
    Get this guild's auto moderation rules.

    Returns:
        A list of auto moderation rules
    """
    data = await self._client.http.get_auto_moderation_rules(self.id)
    return [AutoModRule.from_dict(d, self._client) for d in data]

delete_auto_moderation_rule(rule, reason=MISSING) async

Delete a given auto moderation rule.

Parameters:

Name Type Description Default
rule Snowflake_Type

The rule to delete

required
reason Absent[str]

The reason for deleting this rule

MISSING
Source code in naff/models/discord/guild.py
1709
1710
1711
1712
1713
1714
1715
1716
1717
async def delete_auto_moderation_rule(self, rule: "Snowflake_Type", reason: Absent[str] = MISSING) -> None:
    """
    Delete a given auto moderation rule.

    Args:
        rule: The rule to delete
        reason: The reason for deleting this rule
    """
    await self._client.http.delete_auto_moderation_rule(self.id, to_snowflake(rule), reason=reason)

modify_auto_moderation_rule(rule, *, name=MISSING, trigger=MISSING, trigger_type=MISSING, trigger_metadata=MISSING, actions=MISSING, exempt_channels=MISSING, exempt_roles=MISSING, event_type=MISSING, enabled=MISSING, reason=MISSING) async

Modify an existing automod rule.

Parameters:

Name Type Description Default
rule Snowflake_Type

The rule to modify

required
name Absent[str]

The name of the rule

MISSING
trigger Absent[BaseTrigger]

A trigger for this rule

MISSING
trigger_type Absent[AutoModTriggerType]

The type trigger for this rule (ignored if trigger specified)

MISSING
trigger_metadata Absent[dict]

Metadata for the trigger (ignored if trigger specified)

MISSING
actions Absent[list[BaseAction]]

A list of actions to take upon triggering

MISSING
exempt_roles Absent[list[Snowflake_Type]]

Roles that ignore this rule

MISSING
exempt_channels Absent[list[Snowflake_Type]]

Channels that ignore this role

MISSING
enabled Absent[bool]

Is this rule enabled?

MISSING
event_type Absent[AutoModEvent]

The type of event that triggers this rule

MISSING
reason Absent[str]

The reason for this change

MISSING

Returns:

Type Description
AutoModRule

The updated rule

Source code in naff/models/discord/guild.py
1719
1720
1721
1722
1723
1724
1725
1726
1727
1728
1729
1730
1731
1732
1733
1734
1735
1736
1737
1738
1739
1740
1741
1742
1743
1744
1745
1746
1747
1748
1749
1750
1751
1752
1753
1754
1755
1756
1757
1758
1759
1760
1761
1762
1763
1764
1765
1766
1767
1768
1769
1770
1771
async def modify_auto_moderation_rule(
    self,
    rule: "Snowflake_Type",
    *,
    name: Absent[str] = MISSING,
    trigger: Absent[BaseTrigger] = MISSING,
    trigger_type: Absent[AutoModTriggerType] = MISSING,
    trigger_metadata: Absent[dict] = MISSING,
    actions: Absent[list[BaseAction]] = MISSING,
    exempt_channels: Absent[list["Snowflake_Type"]] = MISSING,
    exempt_roles: Absent[list["Snowflake_Type"]] = MISSING,
    event_type: Absent[AutoModEvent] = MISSING,
    enabled: Absent[bool] = MISSING,
    reason: Absent[str] = MISSING,
) -> AutoModRule:
    """
    Modify an existing automod rule.

    Args:
        rule: The rule to modify
        name: The name of the rule
        trigger: A trigger for this rule
        trigger_type: The type trigger for this rule (ignored if trigger specified)
        trigger_metadata: Metadata for the trigger (ignored if trigger specified)
        actions: A list of actions to take upon triggering
        exempt_roles: Roles that ignore this rule
        exempt_channels: Channels that ignore this role
        enabled: Is this rule enabled?
        event_type: The type of event that triggers this rule
        reason: The reason for this change

    Returns:
        The updated rule
    """
    if trigger:
        _data = trigger.to_dict()
        trigger_type = _data["trigger_type"]
        trigger_metadata = _data.get("trigger_metadata", {})

    out = await self._client.http.modify_auto_moderation_rule(
        self.id,
        to_snowflake(rule),
        name=name,
        trigger_type=trigger_type,
        trigger_metadata=trigger_metadata,
        actions=actions,
        exempt_roles=to_snowflake_list(exempt_roles) if exempt_roles is not MISSING else MISSING,
        exempt_channels=to_snowflake_list(exempt_channels) if exempt_channels is not MISSING else MISSING,
        event_type=event_type,
        enabled=enabled,
        reason=reason,
    )
    return AutoModRule.from_dict(out, self._client)

unban(user, reason=MISSING) async

Unban a user from the guild.

Note

You must have the ban members permission

Parameters:

Name Type Description Default
user Union[User, Member, Snowflake_Type]

The user to unban

required
reason Absent[str]

The reason for the ban

MISSING
Source code in naff/models/discord/guild.py
1773
1774
1775
1776
1777
1778
1779
1780
1781
1782
1783
1784
1785
1786
1787
async def unban(
    self, user: Union["models.User", "models.Member", Snowflake_Type], reason: Absent[str] = MISSING
) -> None:
    """
    Unban a user from the guild.

    !!! note
        You must have the `ban members` permission

    Args:
        user: The user to unban
        reason: The reason for the ban

    """
    await self._client.http.remove_guild_ban(self.id, to_snowflake(user), reason=reason)

fetch_widget_image(style=None) async

Fetch a guilds widget image.

For a list of styles, look here: https://discord.com/developers/docs/resources/guild#get-guild-widget-image-widget-style-options

Parameters:

Name Type Description Default
style str

The style to use for the widget image

None

Returns:

Type Description
str

The URL of the widget image.

Source code in naff/models/discord/guild.py
1789
1790
1791
1792
1793
1794
1795
1796
1797
1798
1799
1800
1801
1802
async def fetch_widget_image(self, style: str = None) -> str:
    """
    Fetch a guilds widget image.

    For a list of styles, look here: https://discord.com/developers/docs/resources/guild#get-guild-widget-image-widget-style-options

    Args:
        style: The style to use for the widget image

    Returns:
        The URL of the widget image.

    """
    return await self._client.http.get_guild_widget_image(self.id, style)

fetch_widget_settings() async

Fetches the guilds widget settings.

Returns:

Type Description
GuildWidgetSettings

The guilds widget settings object.

Source code in naff/models/discord/guild.py
1804
1805
1806
1807
1808
1809
1810
1811
1812
async def fetch_widget_settings(self) -> "GuildWidgetSettings":
    """
    Fetches the guilds widget settings.

    Returns:
        The guilds widget settings object.

    """
    return await GuildWidgetSettings.from_dict(await self._client.http.get_guild_widget_settings(self.id))

fetch_widget() async

Fetches the guilds widget.

Returns:

Type Description
GuildWidget

The guilds widget object.

Source code in naff/models/discord/guild.py
1814
1815
1816
1817
1818
1819
1820
1821
1822
async def fetch_widget(self) -> "GuildWidget":
    """
    Fetches the guilds widget.

    Returns:
        The guilds widget object.

    """
    return GuildWidget.from_dict(await self._client.http.get_guild_widget(self.id), self._client)

modify_widget(enabled=MISSING, channel=MISSING, settings=MISSING) async

Modify the guild's widget.

Parameters:

Name Type Description Default
enabled Absent[bool]

Should the widget be enabled?

MISSING
channel Absent[Union[TYPE_GUILD_CHANNEL, Snowflake_Type]]

The channel to use in the widget

MISSING
settings Absent[GuildWidgetSettings]

The settings to use for the widget

MISSING

Returns:

Type Description
GuildWidget

The updated guilds widget object.

Source code in naff/models/discord/guild.py
1824
1825
1826
1827
1828
1829
1830
1831
1832
1833
1834
1835
1836
1837
1838
1839
1840
1841
1842
1843
1844
1845
1846
1847
1848
1849
async def modify_widget(
    self,
    enabled: Absent[bool] = MISSING,
    channel: Absent[Union["models.TYPE_GUILD_CHANNEL", Snowflake_Type]] = MISSING,
    settings: Absent["GuildWidgetSettings"] = MISSING,
) -> "GuildWidget":
    """
    Modify the guild's widget.

    Args:
        enabled: Should the widget be enabled?
        channel: The channel to use in the widget
        settings: The settings to use for the widget

    Returns:
        The updated guilds widget object.

    """
    if isinstance(settings, GuildWidgetSettings):
        enabled = settings.enabled
        channel = settings.channel_id

    channel = to_optional_snowflake(channel)
    return GuildWidget.from_dict(
        await self._client.http.modify_guild_widget(self.id, enabled, channel), self._client
    )

fetch_invites() async

Fetches all invites for the guild.

Returns:

Type Description
List[Invite]

A list of invites for the guild.

Source code in naff/models/discord/guild.py
1851
1852
1853
1854
1855
1856
1857
1858
1859
1860
async def fetch_invites(self) -> List["models.Invite"]:
    """
    Fetches all invites for the guild.

    Returns:
        A list of invites for the guild.

    """
    invites_data = await self._client.http.get_guild_invites(self.id)
    return models.Invite.from_list(invites_data, self._client)

fetch_guild_integrations() async

Fetches all integrations for the guild.

Returns:

Type Description
List[GuildIntegration]

A list of integrations for the guild.

Source code in naff/models/discord/guild.py
1862
1863
1864
1865
1866
1867
1868
1869
1870
1871
async def fetch_guild_integrations(self) -> List["models.GuildIntegration"]:
    """
    Fetches all integrations for the guild.

    Returns:
        A list of integrations for the guild.

    """
    data = await self._client.http.get_guild_integrations(self.id)
    return [GuildIntegration.from_dict(d | {"guild_id": self.id}, self._client) for d in data]

search_members(query, limit=1) async

Search for members in the guild whose username or nickname starts with a provided string.

Parameters:

Name Type Description Default
query str

Query string to match username(s) and nickname(s) against.

required
limit int

Max number of members to return (1-1000)

1

Returns:

Type Description
List[Member]

A list of members matching the query.

Source code in naff/models/discord/guild.py
1873
1874
1875
1876
1877
1878
1879
1880
1881
1882
1883
1884
1885
1886
async def search_members(self, query: str, limit: int = 1) -> List["models.Member"]:
    """
    Search for members in the guild whose username or nickname starts with a provided string.

    Args:
        query: Query string to match username(s) and nickname(s) against.
        limit: Max number of members to return (1-1000)

    Returns:
        A list of members matching the query.

    """
    data = await self._client.http.search_guild_members(guild_id=self.id, query=query, limit=limit)
    return [self._client.cache.place_member_data(self.id, _d) for _d in data]

fetch_voice_regions() async

Fetches the voice regions for the guild.

Unlike the NAFF.fetch_voice_regions method, this will returns VIP servers when the guild is VIP-enabled.

Returns:

Type Description
List[VoiceRegion]

A list of voice regions.

Source code in naff/models/discord/guild.py
1888
1889
1890
1891
1892
1893
1894
1895
1896
1897
1898
1899
1900
async def fetch_voice_regions(self) -> List["models.VoiceRegion"]:
    """
    Fetches the voice regions for the guild.

    Unlike the `NAFF.fetch_voice_regions` method, this will returns VIP servers when the guild is VIP-enabled.

    Returns:
        A list of voice regions.

    """
    regions_data = await self._client.http.get_guild_voice_regions(self.id)
    regions = models.VoiceRegion.from_list(regions_data)
    return regions

gui_sorted_channels() property

Return this guilds channels sorted by their gui positions

Source code in naff/models/discord/guild.py
1902
1903
1904
1905
1906
1907
1908
1909
1910
1911
@property
def gui_sorted_channels(self) -> list["models.TYPE_GUILD_CHANNEL"]:
    """Return this guilds channels sorted by their gui positions"""
    # create a sorted list of objects by their gui position
    if not self._channel_gui_positions:
        self._calculate_gui_channel_positions()
    return [
        self._client.get_channel(k)
        for k, v in sorted(self._channel_gui_positions.items(), key=lambda item: item[1])
    ]

get_channel_gui_position(channel_id)

Get a given channels gui position.

Parameters:

Name Type Description Default
channel_id Snowflake_Type

The ID of the channel to get the gui position for.

required

Returns:

Type Description
int

The gui position of the channel.

Source code in naff/models/discord/guild.py
1913
1914
1915
1916
1917
1918
1919
1920
1921
1922
1923
1924
1925
def get_channel_gui_position(self, channel_id: "Snowflake_Type") -> int:
    """
    Get a given channels gui position.

    Args:
        channel_id: The ID of the channel to get the gui position for.

    Returns:
        The gui position of the channel.
    """
    if not self._channel_gui_positions:
        self._calculate_gui_channel_positions()
    return self._channel_gui_positions.get(to_snowflake(channel_id), 0)

GuildTemplate

Bases: ClientObject

Source code in naff/models/discord/guild.py
1975
1976
1977
1978
1979
1980
1981
1982
1983
1984
1985
1986
1987
1988
1989
1990
1991
1992
1993
1994
1995
1996
1997
1998
1999
2000
2001
2002
2003
2004
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
2027
2028
@define()
class GuildTemplate(ClientObject):
    code: str = field(repr=True, metadata=docs("the template code (unique ID)"))
    name: str = field(repr=True, metadata=docs("the name"))
    description: Optional[str] = field(default=None, metadata=docs("the description"))

    usage_count: int = field(default=0, metadata=docs("number of times this template has been used"))

    creator_id: Snowflake_Type = field(metadata=docs("The ID of the user who created this template"))
    creator: Optional["models.User"] = field(default=None, metadata=docs("the user who created this template"))

    created_at: "models.Timestamp" = field(metadata=docs("When this template was created"))
    updated_at: "models.Timestamp" = field(metadata=docs("When this template was last synced to the source guild"))

    source_guild_id: Snowflake_Type = field(metadata=docs("The ID of the guild this template is based on"))
    guild_snapshot: "models.Guild" = field(metadata=docs("A snapshot of the guild this template contains"))

    is_dirty: bool = field(default=False, metadata=docs("Whether this template has un-synced changes"))

    @classmethod
    def _process_dict(cls, data: Dict[str, Any], client: "Client") -> Dict[str, Any]:
        data["creator"] = client.cache.place_user_data(data["creator"])

        # todo: partial guild obj that **isn't** cached
        data["guild_snapshot"] = data.pop("serialized_source_guild")
        return data

    async def synchronise(self) -> "models.GuildTemplate":
        """Synchronise the template to the source guild's current state."""
        data = await self._client.http.sync_guild_template(self.source_guild_id, self.code)
        self.update_from_dict(data)
        return self

    async def modify(self, name: Absent[str] = MISSING, description: Absent[str] = MISSING) -> "models.GuildTemplate":
        """
        Modify the template's metadata.

        Args:
            name: The name for the template
            description: The description for the template

        Returns:
            The modified template object.

        """
        data = await self._client.http.modify_guild_template(
            self.source_guild_id, self.code, name=name, description=description
        )
        self.update_from_dict(data)
        return self

    async def delete(self) -> None:
        """Delete the guild template."""
        await self._client.http.delete_guild_template(self.source_guild_id, self.code)

synchronise() async

Synchronise the template to the source guild's current state.

Source code in naff/models/discord/guild.py
2002
2003
2004
2005
2006
async def synchronise(self) -> "models.GuildTemplate":
    """Synchronise the template to the source guild's current state."""
    data = await self._client.http.sync_guild_template(self.source_guild_id, self.code)
    self.update_from_dict(data)
    return self

modify(name=MISSING, description=MISSING) async

Modify the template's metadata.

Parameters:

Name Type Description Default
name Absent[str]

The name for the template

MISSING
description Absent[str]

The description for the template

MISSING

Returns:

Type Description
GuildTemplate

The modified template object.

Source code in naff/models/discord/guild.py
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
async def modify(self, name: Absent[str] = MISSING, description: Absent[str] = MISSING) -> "models.GuildTemplate":
    """
    Modify the template's metadata.

    Args:
        name: The name for the template
        description: The description for the template

    Returns:
        The modified template object.

    """
    data = await self._client.http.modify_guild_template(
        self.source_guild_id, self.code, name=name, description=description
    )
    self.update_from_dict(data)
    return self

delete() async

Delete the guild template.

Source code in naff/models/discord/guild.py
2026
2027
2028
async def delete(self) -> None:
    """Delete the guild template."""
    await self._client.http.delete_guild_template(self.source_guild_id, self.code)

GuildIntegration

Bases: DiscordObject

Source code in naff/models/discord/guild.py
2043
2044
2045
2046
2047
2048
2049
2050
2051
2052
2053
2054
2055
2056
2057
2058
2059
2060
2061
2062
2063
2064
2065
2066
2067
2068
2069
2070
2071
2072
2073
2074
2075
2076
2077
2078
2079
2080
2081
2082
2083
2084
2085
2086
class GuildIntegration(DiscordObject):
    name: str = field(repr=True)
    """The name of the integration"""
    type: str = field(repr=True)
    """integration type (twitch, youtube, or discord)"""
    enabled: bool = field(repr=True)
    """is this integration enabled"""
    account: dict = field()
    """integration account information"""
    application: Optional["models.Application"] = field(default=None)
    """The bot/OAuth2 application for discord integrations"""
    _guild_id: Snowflake_Type = field()

    syncing: Optional[bool] = field(default=MISSING)
    """is this integration syncing"""
    role_id: Optional[Snowflake_Type] = field(default=MISSING)
    """id that this integration uses for "subscribers\""""
    enable_emoticons: bool = field(default=MISSING)
    """whether emoticons should be synced for this integration (twitch only currently)"""
    expire_behavior: IntegrationExpireBehaviour = field(default=MISSING, converter=optional(IntegrationExpireBehaviour))
    """the behavior of expiring subscribers"""
    expire_grace_period: int = field(default=MISSING)
    """the grace period (in days) before expiring subscribers"""
    user: "models.BaseUser" = field(default=MISSING)
    """user for this integration"""
    synced_at: "models.Timestamp" = field(default=MISSING, converter=optional(timestamp_converter))
    """when this integration was last synced"""
    subscriber_count: int = field(default=MISSING)
    """how many subscribers this integration has"""
    revoked: bool = field(default=MISSING)
    """has this integration been revoked"""

    @classmethod
    def _process_dict(cls, data: Dict[str, Any], client: "Client") -> Dict[str, Any]:
        if app := data.get("application", None):
            data["application"] = models.Application.from_dict(app, client)
        if user := data.get("user", None):
            data["user"] = client.cache.place_user_data(user)

        return data

    async def delete(self, reason: Absent[str] = MISSING) -> None:
        """Delete this guild integration."""
        await self._client.http.delete_guild_integration(self._guild_id, self.id, reason)

name: str = field(repr=True) class-attribute

The name of the integration

type: str = field(repr=True) class-attribute

integration type (twitch, youtube, or discord)

enabled: bool = field(repr=True) class-attribute

is this integration enabled

account: dict = field() class-attribute

integration account information

application: Optional[models.Application] = field(default=None) class-attribute

The bot/OAuth2 application for discord integrations

syncing: Optional[bool] = field(default=MISSING) class-attribute

is this integration syncing

role_id: Optional[Snowflake_Type] = field(default=MISSING) class-attribute

id that this integration uses for "subscribers"

enable_emoticons: bool = field(default=MISSING) class-attribute

whether emoticons should be synced for this integration (twitch only currently)

expire_behavior: IntegrationExpireBehaviour = field(default=MISSING, converter=optional(IntegrationExpireBehaviour)) class-attribute

the behavior of expiring subscribers

expire_grace_period: int = field(default=MISSING) class-attribute

the grace period (in days) before expiring subscribers

user: models.BaseUser = field(default=MISSING) class-attribute

user for this integration

synced_at: models.Timestamp = field(default=MISSING, converter=optional(timestamp_converter)) class-attribute

when this integration was last synced

subscriber_count: int = field(default=MISSING) class-attribute

how many subscribers this integration has

revoked: bool = field(default=MISSING) class-attribute

has this integration been revoked

delete(reason=MISSING) async

Delete this guild integration.

Source code in naff/models/discord/guild.py
2084
2085
2086
async def delete(self, reason: Absent[str] = MISSING) -> None:
    """Delete this guild integration."""
    await self._client.http.delete_guild_integration(self._guild_id, self.id, reason)

GuildWidgetSettings

Bases: DictSerializationMixin

Source code in naff/models/discord/guild.py
2089
2090
2091
2092
2093
class GuildWidgetSettings(DictSerializationMixin):
    enabled: bool = field(repr=True, default=False)
    """Whether the widget is enabled."""
    channel_id: Optional["Snowflake_Type"] = field(repr=True, default=None, converter=to_optional_snowflake)
    """The widget channel id. None if widget is not enabled."""

enabled: bool = field(repr=True, default=False) class-attribute

Whether the widget is enabled.

channel_id: Optional[Snowflake_Type] = field(repr=True, default=None, converter=to_optional_snowflake) class-attribute

The widget channel id. None if widget is not enabled.

GuildWidget

Bases: DiscordObject

Source code in naff/models/discord/guild.py
2096
2097
2098
2099
2100
2101
2102
2103
2104
2105
2106
2107
2108
2109
2110
2111
2112
2113
2114
2115
2116
2117
2118
2119
2120
2121
2122
2123
2124
2125
2126
2127
2128
2129
2130
2131
2132
2133
2134
2135
2136
2137
2138
2139
2140
2141
2142
2143
2144
2145
2146
2147
2148
2149
2150
2151
2152
2153
2154
2155
class GuildWidget(DiscordObject):
    name: str = field(repr=True)
    """Guild name (2-100 characters)"""
    instant_invite: str = field(repr=True, default=None)
    """Instant invite for the guilds specified widget invite channel"""
    presence_count: int = field(repr=True, default=0)
    """Number of online members in this guild"""

    _channel_ids: List["Snowflake_Type"] = field(default=[])
    """Voice and stage channels which are accessible by @everyone"""
    _member_ids: List["Snowflake_Type"] = field(default=[])
    """Special widget user objects that includes users presence (Limit 100)"""

    @classmethod
    def _process_dict(cls, data: Dict[str, Any], client: "Client") -> Dict[str, Any]:
        if channels := data.get("channels"):
            data["channel_ids"] = [channel["id"] for channel in channels]
        if members := data.get("members"):
            data["member_ids"] = [member["id"] for member in members]
        return data

    def get_channels(self) -> List["models.TYPE_VOICE_CHANNEL"]:
        """
        Gets voice and stage channels which are accessible by @everyone

        Returns:
            List of channels

        """
        return [self._client.get_channel(channel_id) for channel_id in self._channel_ids]

    async def fetch_channels(self) -> List["models.TYPE_VOICE_CHANNEL"]:
        """
        Gets voice and stage channels which are accessible by @everyone. Fetches the channels from API if they are not cached.

        Returns:
            List of channels

        """
        return [await self._client.fetch_channel(channel_id) for channel_id in self._channel_ids]

    def get_members(self) -> List["models.User"]:
        """
        Gets special widget user objects that includes users presence (Limit 100)

        Returns:
            List of users

        """
        return [self._client.get_user(member_id) for member_id in self._member_ids]

    async def fetch_members(self) -> List["models.User"]:
        """
        Gets special widget user objects that includes users presence (Limit 100). Fetches the users from API if they are not cached.

        Returns:
            List of users

        """
        return [await self._client.fetch_user(member_id) for member_id in self._member_ids]

name: str = field(repr=True) class-attribute

Guild name (2-100 characters)

instant_invite: str = field(repr=True, default=None) class-attribute

Instant invite for the guilds specified widget invite channel

presence_count: int = field(repr=True, default=0) class-attribute

Number of online members in this guild

get_channels()

Gets voice and stage channels which are accessible by @everyone

Returns:

Type Description
List[TYPE_VOICE_CHANNEL]

List of channels

Source code in naff/models/discord/guild.py
2117
2118
2119
2120
2121
2122
2123
2124
2125
def get_channels(self) -> List["models.TYPE_VOICE_CHANNEL"]:
    """
    Gets voice and stage channels which are accessible by @everyone

    Returns:
        List of channels

    """
    return [self._client.get_channel(channel_id) for channel_id in self._channel_ids]

fetch_channels() async

Gets voice and stage channels which are accessible by @everyone. Fetches the channels from API if they are not cached.

Returns:

Type Description
List[TYPE_VOICE_CHANNEL]

List of channels

Source code in naff/models/discord/guild.py
2127
2128
2129
2130
2131
2132
2133
2134
2135
async def fetch_channels(self) -> List["models.TYPE_VOICE_CHANNEL"]:
    """
    Gets voice and stage channels which are accessible by @everyone. Fetches the channels from API if they are not cached.

    Returns:
        List of channels

    """
    return [await self._client.fetch_channel(channel_id) for channel_id in self._channel_ids]

get_members()

Gets special widget user objects that includes users presence (Limit 100)

Returns:

Type Description
List[User]

List of users

Source code in naff/models/discord/guild.py
2137
2138
2139
2140
2141
2142
2143
2144
2145
def get_members(self) -> List["models.User"]:
    """
    Gets special widget user objects that includes users presence (Limit 100)

    Returns:
        List of users

    """
    return [self._client.get_user(member_id) for member_id in self._member_ids]

fetch_members() async

Gets special widget user objects that includes users presence (Limit 100). Fetches the users from API if they are not cached.

Returns:

Type Description
List[User]

List of users

Source code in naff/models/discord/guild.py
2147
2148
2149
2150
2151
2152
2153
2154
2155
async def fetch_members(self) -> List["models.User"]:
    """
    Gets special widget user objects that includes users presence (Limit 100). Fetches the users from API if they are not cached.

    Returns:
        List of users

    """
    return [await self._client.fetch_user(member_id) for member_id in self._member_ids]

AuditLogChange

Bases: ClientObject

Source code in naff/models/discord/guild.py
2158
2159
2160
2161
2162
2163
2164
2165
@define()
class AuditLogChange(ClientObject):
    key: str = field(repr=True)
    """name of audit log change key"""
    new_value: Optional[Union[list, str, int, bool, "Snowflake_Type"]] = field(default=MISSING)
    """new value of the key"""
    old_value: Optional[Union[list, str, int, bool, "Snowflake_Type"]] = field(default=MISSING)
    """old value of the key"""

key: str = field(repr=True) class-attribute

name of audit log change key

new_value: Optional[Union[list, str, int, bool, Snowflake_Type]] = field(default=MISSING) class-attribute

new value of the key

old_value: Optional[Union[list, str, int, bool, Snowflake_Type]] = field(default=MISSING) class-attribute

old value of the key

AuditLogEntry

Bases: DiscordObject

Source code in naff/models/discord/guild.py
2168
2169
2170
2171
2172
2173
2174
2175
2176
2177
2178
2179
2180
2181
2182
2183
2184
2185
2186
2187
2188
@define()
class AuditLogEntry(DiscordObject):
    target_id: Optional["Snowflake_Type"] = field(converter=optional(to_snowflake))
    """id of the affected entity (webhook, user, role, etc.)"""
    user_id: "Snowflake_Type" = field(converter=optional(to_snowflake))
    """the user who made the changes"""
    action_type: "AuditLogEventType" = field(converter=AuditLogEventType)
    """type of action that occurred"""
    changes: Optional[List[AuditLogChange]] = field(default=MISSING)
    """changes made to the target_id"""
    options: Optional[Union["Snowflake_Type", str]] = field(default=MISSING)
    """additional info for certain action types"""
    reason: Optional[str] = field(default=MISSING)
    """the reason for the change (0-512 characters)"""

    @classmethod
    def _process_dict(cls, data: Dict[str, Any], client: "Client") -> Dict[str, Any]:
        if changes := data.get("changes", None):
            data["changes"] = AuditLogChange.from_list(changes, client)

        return data

target_id: Optional[Snowflake_Type] = field(converter=optional(to_snowflake)) class-attribute

id of the affected entity (webhook, user, role, etc.)

user_id: Snowflake_Type = field(converter=optional(to_snowflake)) class-attribute

the user who made the changes

action_type: AuditLogEventType = field(converter=AuditLogEventType) class-attribute

type of action that occurred

changes: Optional[List[AuditLogChange]] = field(default=MISSING) class-attribute

changes made to the target_id

options: Optional[Union[Snowflake_Type, str]] = field(default=MISSING) class-attribute

additional info for certain action types

reason: Optional[str] = field(default=MISSING) class-attribute

the reason for the change (0-512 characters)

AuditLog

Bases: ClientObject

Contains entries and other data given from selected

Source code in naff/models/discord/guild.py
2191
2192
2193
2194
2195
2196
2197
2198
2199
2200
2201
2202
2203
2204
2205
2206
2207
2208
2209
2210
2211
2212
2213
2214
2215
2216
2217
2218
2219
2220
2221
2222
2223
2224
2225
@define()
class AuditLog(ClientObject):
    """Contains entries and other data given from selected"""

    application_commands: list["InteractionCommand"] = field(factory=list, converter=optional(deserialize_app_cmds))
    """list of application commands that have had their permissions updated"""
    entries: Optional[List["AuditLogEntry"]] = field(default=MISSING)
    """list of audit log entries"""
    scheduled_events: Optional[List["models.ScheduledEvent"]] = field(default=MISSING)
    """list of guild scheduled events found in the audit log"""
    integrations: Optional[List["GuildIntegration"]] = field(default=MISSING)
    """list of partial integration objects"""
    threads: Optional[List["models.ThreadChannel"]] = field(default=MISSING)
    """list of threads found in the audit log"""
    users: Optional[List["models.User"]] = field(default=MISSING)
    """list of users found in the audit log"""
    webhooks: Optional[List["models.Webhook"]] = field(default=MISSING)
    """list of webhooks found in the audit log"""

    @classmethod
    def _process_dict(cls, data: Dict[str, Any], client: "Client") -> Dict[str, Any]:
        if entries := data.get("audit_log_entries", None):
            data["entries"] = AuditLogEntry.from_list(entries, client)
        if scheduled_events := data.get("guild_scheduled_events", None):
            data["scheduled_events"] = models.ScheduledEvent.from_list(scheduled_events, client)
        if integrations := data.get("integrations", None):
            data["integrations"] = GuildIntegration.from_list(integrations, client)
        if threads := data.get("threads", None):
            data["threads"] = models.ThreadChannel.from_list(threads, client)
        if users := data.get("users", None):
            data["users"] = models.User.from_list(users, client)
        if webhooks := data.get("webhooks", None):
            data["webhooks"] = models.Webhook.from_list(webhooks, client)

        return data

application_commands: list[InteractionCommand] = field(factory=list, converter=optional(deserialize_app_cmds)) class-attribute

list of application commands that have had their permissions updated

entries: Optional[List[AuditLogEntry]] = field(default=MISSING) class-attribute

list of audit log entries

scheduled_events: Optional[List[models.ScheduledEvent]] = field(default=MISSING) class-attribute

list of guild scheduled events found in the audit log

integrations: Optional[List[GuildIntegration]] = field(default=MISSING) class-attribute

list of partial integration objects

threads: Optional[List[models.ThreadChannel]] = field(default=MISSING) class-attribute

list of threads found in the audit log

users: Optional[List[models.User]] = field(default=MISSING) class-attribute

list of users found in the audit log

webhooks: Optional[List[models.Webhook]] = field(default=MISSING) class-attribute

list of webhooks found in the audit log

AuditLogHistory

Bases: AsyncIterator

An async iterator for searching through a audit log's entry history.

Attributes:

Name Type Description
guild
user_id
action_type
before Snowflake_Type

get messages before this message ID

after Snowflake_Type

get messages after this message ID

limit Snowflake_Type

The maximum number of entries to return (set to 0 for no limit)

Source code in naff/models/discord/guild.py
2228
2229
2230
2231
2232
2233
2234
2235
2236
2237
2238
2239
2240
2241
2242
2243
2244
2245
2246
2247
2248
2249
2250
2251
2252
2253
2254
2255
2256
2257
2258
2259
2260
2261
2262
2263
2264
2265
2266
2267
2268
2269
2270
2271
2272
2273
2274
2275
2276
2277
2278
2279
2280
class AuditLogHistory(AsyncIterator):
    """
    An async iterator for searching through a audit log's entry history.

    Attributes:
        guild (:class:`Guild`): The guild to search through.
        user_id (:class:`Snowflake_Type`): The user ID to search for.
        action_type (:class:`AuditLogEventType`): The action type to search for.
        before: get messages before this message ID
        after: get messages after this message ID
        limit: The maximum number of entries to return (set to 0 for no limit)

    """

    def __init__(
        self,
        guild: "Guild",
        user_id: Snowflake_Type = None,
        action_type: "AuditLogEventType" = None,
        before: Snowflake_Type = None,
        after: Snowflake_Type = None,
        limit: int = 50,
    ) -> None:
        self.guild: "Guild" = guild
        self.user_id: Snowflake_Type = user_id
        self.action_type: "AuditLogEventType" = action_type
        self.before: Snowflake_Type = before
        self.after: Snowflake_Type = after
        super().__init__(limit)

    async def fetch(self) -> List["AuditLog"]:
        """
        Retrieves the audit log entries from discord API.

        Returns:
            The list of audit log entries.

        """
        if self.after:
            if not self.last:
                self.last = namedtuple("temp", "id")
                self.last.id = self.after
            log = await self.guild.fetch_audit_log(limit=self.get_limit, after=self.last.id)
            entries = log.entries if log.entries else []

        else:
            if self.before and not self.last:
                self.last = namedtuple("temp", "id")
                self.last.id = self.before

            log = await self.guild.fetch_audit_log(limit=self.get_limit, before=self.last.id)
            entries = log.entries if log.entries else []
        return entries

fetch() async

Retrieves the audit log entries from discord API.

Returns:

Type Description
List[AuditLog]

The list of audit log entries.

Source code in naff/models/discord/guild.py
2258
2259
2260
2261
2262
2263
2264
2265
2266
2267
2268
2269
2270
2271
2272
2273
2274
2275
2276
2277
2278
2279
2280
async def fetch(self) -> List["AuditLog"]:
    """
    Retrieves the audit log entries from discord API.

    Returns:
        The list of audit log entries.

    """
    if self.after:
        if not self.last:
            self.last = namedtuple("temp", "id")
            self.last.id = self.after
        log = await self.guild.fetch_audit_log(limit=self.get_limit, after=self.last.id)
        entries = log.entries if log.entries else []

    else:
        if self.before and not self.last:
            self.last = namedtuple("temp", "id")
            self.last.id = self.before

        log = await self.guild.fetch_audit_log(limit=self.get_limit, before=self.last.id)
        entries = log.entries if log.entries else []
    return entries

ScheduledEvent

Bases: DiscordObject

Source code in naff/models/discord/scheduled_event.py
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
@define()
class ScheduledEvent(DiscordObject):
    name: str = field(repr=True)
    description: str = field(default=MISSING)
    entity_type: Union[ScheduledEventType, int] = field(converter=ScheduledEventType)
    """The type of the scheduled event"""
    start_time: Timestamp = field(converter=timestamp_converter)
    """A Timestamp object representing the scheduled start time of the event """
    end_time: Optional[Timestamp] = field(default=None, converter=optional(timestamp_converter))
    """Optional Timstamp object representing the scheduled end time, required if entity_type is EXTERNAL"""
    privacy_level: Union[ScheduledEventPrivacyLevel, int] = field(converter=ScheduledEventPrivacyLevel)
    """
    Privacy level of the scheduled event

    ??? note
        Discord only has `GUILD_ONLY` at the momment.
    """
    status: Union[ScheduledEventStatus, int] = field(converter=ScheduledEventStatus)
    """Current status of the scheduled event"""
    entity_id: Optional["Snowflake_Type"] = field(default=MISSING, converter=optional(to_snowflake))
    """The id of an entity associated with a guild scheduled event"""
    entity_metadata: Optional[Dict[str, Any]] = field(default=MISSING)  # TODO make this
    """The metadata associated with the entity_type"""
    user_count: int = field(default=MISSING)
    """Amount of users subscribed to the scheduled event"""
    cover: Asset | None = field(default=None)
    """The cover image of this event"""

    _guild_id: "Snowflake_Type" = field(converter=to_snowflake)
    _creator: Optional["User"] = field(default=MISSING)
    _creator_id: Optional["Snowflake_Type"] = field(default=MISSING, converter=optional(to_snowflake))
    _channel_id: Optional["Snowflake_Type"] = field(default=None, converter=optional(to_snowflake))

    @property
    async def creator(self) -> Optional["User"]:
        """
        Returns the user who created this event.

        !!! note
            Events made before October 25th, 2021 will not have a creator.

        """
        return await self._client.cache.fetch_user(self._creator_id) if self._creator_id else None

    @property
    def guild(self) -> "Guild":
        return self._client.cache.get_guild(self._guild_id)

    @classmethod
    def _process_dict(cls, data: Dict[str, Any], client: "Client") -> Dict[str, Any]:
        if data.get("creator"):
            data["creator"] = client.cache.place_user_data(data["creator"])

        if data.get("channel_id"):
            data["channel"] = client.cache.get_channel(data["channel_id"])

        data["start_time"] = data.get("scheduled_start_time")

        if end_time := data.get("scheduled_end_time"):
            data["end_time"] = end_time
        else:
            data["end_time"] = None

        if image := data.get("image"):
            data["cover"] = Asset.from_path_hash(client, f"guild-events/{data['id']}/{{}}", image)

        data = super()._process_dict(data, client)
        return data

    @property
    def location(self) -> Optional[str]:
        """Returns the external locatian of this event."""
        if self.entity_type == ScheduledEventType.EXTERNAL:
            return self.entity_metadata["location"]
        return None

    async def fetch_channel(self) -> Optional[Union["GuildVoice", "GuildStageVoice"]]:
        """Returns the channel this event is scheduled in if it is scheduled in a channel."""
        if self._channel_id:
            return await self._client.cache.fetch_channel(self._channel_id)
        return None

    def get_channel(self) -> Optional[Union["GuildVoice", "GuildStageVoice"]]:
        """Returns the channel this event is scheduled in if it is scheduled in a channel."""
        if self._channel_id:
            channel = self._client.cache.get_channel(self._channel_id)
            return channel
        return None

    async def fetch_event_users(
        self,
        limit: Optional[int] = 100,
        with_member_data: bool = False,
        before: Absent[Optional["Snowflake_Type"]] = MISSING,
        after: Absent[Optional["Snowflake_Type"]] = MISSING,
    ) -> List[Union["Member", "User"]]:
        """
        Fetch event users.

        Args:
            limit: Discord defualts to 100
            with_member_data: Whether to include guild member data
            before: Snowflake of a user to get before
            after: Snowflake of a user to get after

        !!! note
            This method is paginated

        """
        event_users = await self._client.http.get_scheduled_event_users(
            self._guild_id, self.id, limit, with_member_data, before, after
        )
        participants = []
        for u in event_users:
            if member := u.get("member"):
                u["member"]["user"] = u["user"]
                participants.append(self._client.cache.place_member_data(self._guild_id, member))
            else:
                participants.append(self._client.cache.place_user_data(u["user"]))

        return participants

    async def delete(self, reason: Absent[str] = MISSING) -> None:
        """
        Deletes this event.

        Args:
            reason: The reason for deleting this event

        """
        await self._client.http.delete_scheduled_event(self._guild_id, self.id, reason)

    async def edit(
        self,
        name: Absent[str] = MISSING,
        start_time: Absent["Timestamp"] = MISSING,
        end_time: Absent["Timestamp"] = MISSING,
        status: Absent[ScheduledEventStatus] = MISSING,
        description: Absent[str] = MISSING,
        channel_id: Absent[Optional["Snowflake_Type"]] = MISSING,
        event_type: Absent[ScheduledEventType] = MISSING,
        external_location: Absent[Optional[str]] = MISSING,
        entity_metadata: Absent[dict] = MISSING,
        privacy_level: Absent[ScheduledEventPrivacyLevel] = MISSING,
        cover_image: Absent[UPLOADABLE_TYPE] = MISSING,
        reason: Absent[str] = MISSING,
    ) -> None:
        """
        Edits this event.

        Args:
            name: The name of the event
            description: The description of the event
            channel_id: The channel id of the event
            event_type: The type of the event
            start_time: The scheduled start time of the event
            end_time: The scheduled end time of the event
            status: The status of the event
            entity_metadata: The metadata of the event
            privacy_level: The privacy level of the event
            cover_image: the cover image of the scheduled event
            reason: The reason for editing the event

        !!! note
            If updating event_type to EXTERNAL:
                `channel_id` is required and must be set to null

                `external_location` or `entity_metadata` with a location field must be provided

                `end_time` must be provided

        """
        if external_location is not MISSING:
            entity_metadata = {"location": external_location}

        if event_type == ScheduledEventType.EXTERNAL:
            channel_id = None
            if external_location == MISSING:
                raise EventLocationNotProvided("Location is required for external events")

        payload = {
            "name": name,
            "description": description,
            "channel_id": channel_id,
            "entity_type": event_type,
            "scheduled_start_time": start_time.isoformat() if start_time else MISSING,
            "scheduled_end_time": end_time.isoformat() if end_time else MISSING,
            "status": status,
            "entity_metadata": entity_metadata,
            "privacy_level": privacy_level,
            "image": to_image_data(cover_image) if cover_image else MISSING,
        }
        await self._client.http.modify_scheduled_event(self._guild_id, self.id, payload, reason)

entity_type: Union[ScheduledEventType, int] = field(converter=ScheduledEventType) class-attribute

The type of the scheduled event

start_time: Timestamp = field(converter=timestamp_converter) class-attribute

A Timestamp object representing the scheduled start time of the event

end_time: Optional[Timestamp] = field(default=None, converter=optional(timestamp_converter)) class-attribute

Optional Timstamp object representing the scheduled end time, required if entity_type is EXTERNAL

privacy_level: Union[ScheduledEventPrivacyLevel, int] = field(converter=ScheduledEventPrivacyLevel) class-attribute

Privacy level of the scheduled event

Note

Discord only has GUILD_ONLY at the momment.

status: Union[ScheduledEventStatus, int] = field(converter=ScheduledEventStatus) class-attribute

Current status of the scheduled event

entity_id: Optional[Snowflake_Type] = field(default=MISSING, converter=optional(to_snowflake)) class-attribute

The id of an entity associated with a guild scheduled event

entity_metadata: Optional[Dict[str, Any]] = field(default=MISSING) class-attribute

The metadata associated with the entity_type

user_count: int = field(default=MISSING) class-attribute

Amount of users subscribed to the scheduled event

cover: Asset | None = field(default=None) class-attribute

The cover image of this event

creator() async property

Returns the user who created this event.

Note

Events made before October 25th, 2021 will not have a creator.

Source code in naff/models/discord/scheduled_event.py
59
60
61
62
63
64
65
66
67
68
@property
async def creator(self) -> Optional["User"]:
    """
    Returns the user who created this event.

    !!! note
        Events made before October 25th, 2021 will not have a creator.

    """
    return await self._client.cache.fetch_user(self._creator_id) if self._creator_id else None

location() property

Returns the external locatian of this event.

Source code in naff/models/discord/scheduled_event.py
 95
 96
 97
 98
 99
100
@property
def location(self) -> Optional[str]:
    """Returns the external locatian of this event."""
    if self.entity_type == ScheduledEventType.EXTERNAL:
        return self.entity_metadata["location"]
    return None

fetch_channel() async

Returns the channel this event is scheduled in if it is scheduled in a channel.

Source code in naff/models/discord/scheduled_event.py
102
103
104
105
106
async def fetch_channel(self) -> Optional[Union["GuildVoice", "GuildStageVoice"]]:
    """Returns the channel this event is scheduled in if it is scheduled in a channel."""
    if self._channel_id:
        return await self._client.cache.fetch_channel(self._channel_id)
    return None

get_channel()

Returns the channel this event is scheduled in if it is scheduled in a channel.

Source code in naff/models/discord/scheduled_event.py
108
109
110
111
112
113
def get_channel(self) -> Optional[Union["GuildVoice", "GuildStageVoice"]]:
    """Returns the channel this event is scheduled in if it is scheduled in a channel."""
    if self._channel_id:
        channel = self._client.cache.get_channel(self._channel_id)
        return channel
    return None

fetch_event_users(limit=100, with_member_data=False, before=MISSING, after=MISSING) async

Fetch event users.

Parameters:

Name Type Description Default
limit Optional[int]

Discord defualts to 100

100
with_member_data bool

Whether to include guild member data

False
before Absent[Optional[Snowflake_Type]]

Snowflake of a user to get before

MISSING
after Absent[Optional[Snowflake_Type]]

Snowflake of a user to get after

MISSING

Note

This method is paginated

Source code in naff/models/discord/scheduled_event.py
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
async def fetch_event_users(
    self,
    limit: Optional[int] = 100,
    with_member_data: bool = False,
    before: Absent[Optional["Snowflake_Type"]] = MISSING,
    after: Absent[Optional["Snowflake_Type"]] = MISSING,
) -> List[Union["Member", "User"]]:
    """
    Fetch event users.

    Args:
        limit: Discord defualts to 100
        with_member_data: Whether to include guild member data
        before: Snowflake of a user to get before
        after: Snowflake of a user to get after

    !!! note
        This method is paginated

    """
    event_users = await self._client.http.get_scheduled_event_users(
        self._guild_id, self.id, limit, with_member_data, before, after
    )
    participants = []
    for u in event_users:
        if member := u.get("member"):
            u["member"]["user"] = u["user"]
            participants.append(self._client.cache.place_member_data(self._guild_id, member))
        else:
            participants.append(self._client.cache.place_user_data(u["user"]))

    return participants

delete(reason=MISSING) async

Deletes this event.

Parameters:

Name Type Description Default
reason Absent[str]

The reason for deleting this event

MISSING
Source code in naff/models/discord/scheduled_event.py
148
149
150
151
152
153
154
155
156
async def delete(self, reason: Absent[str] = MISSING) -> None:
    """
    Deletes this event.

    Args:
        reason: The reason for deleting this event

    """
    await self._client.http.delete_scheduled_event(self._guild_id, self.id, reason)

edit(name=MISSING, start_time=MISSING, end_time=MISSING, status=MISSING, description=MISSING, channel_id=MISSING, event_type=MISSING, external_location=MISSING, entity_metadata=MISSING, privacy_level=MISSING, cover_image=MISSING, reason=MISSING) async

Edits this event.

Parameters:

Name Type Description Default
name Absent[str]

The name of the event

MISSING
description Absent[str]

The description of the event

MISSING
channel_id Absent[Optional[Snowflake_Type]]

The channel id of the event

MISSING
event_type Absent[ScheduledEventType]

The type of the event

MISSING
start_time Absent[Timestamp]

The scheduled start time of the event

MISSING
end_time Absent[Timestamp]

The scheduled end time of the event

MISSING
status Absent[ScheduledEventStatus]

The status of the event

MISSING
entity_metadata Absent[dict]

The metadata of the event

MISSING
privacy_level Absent[ScheduledEventPrivacyLevel]

The privacy level of the event

MISSING
cover_image Absent[UPLOADABLE_TYPE]

the cover image of the scheduled event

MISSING
reason Absent[str]

The reason for editing the event

MISSING

Note

If updating event_type to EXTERNAL: channel_id is required and must be set to null

1
2
3
`external_location` or `entity_metadata` with a location field must be provided

`end_time` must be provided
Source code in naff/models/discord/scheduled_event.py
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
async def edit(
    self,
    name: Absent[str] = MISSING,
    start_time: Absent["Timestamp"] = MISSING,
    end_time: Absent["Timestamp"] = MISSING,
    status: Absent[ScheduledEventStatus] = MISSING,
    description: Absent[str] = MISSING,
    channel_id: Absent[Optional["Snowflake_Type"]] = MISSING,
    event_type: Absent[ScheduledEventType] = MISSING,
    external_location: Absent[Optional[str]] = MISSING,
    entity_metadata: Absent[dict] = MISSING,
    privacy_level: Absent[ScheduledEventPrivacyLevel] = MISSING,
    cover_image: Absent[UPLOADABLE_TYPE] = MISSING,
    reason: Absent[str] = MISSING,
) -> None:
    """
    Edits this event.

    Args:
        name: The name of the event
        description: The description of the event
        channel_id: The channel id of the event
        event_type: The type of the event
        start_time: The scheduled start time of the event
        end_time: The scheduled end time of the event
        status: The status of the event
        entity_metadata: The metadata of the event
        privacy_level: The privacy level of the event
        cover_image: the cover image of the scheduled event
        reason: The reason for editing the event

    !!! note
        If updating event_type to EXTERNAL:
            `channel_id` is required and must be set to null

            `external_location` or `entity_metadata` with a location field must be provided

            `end_time` must be provided

    """
    if external_location is not MISSING:
        entity_metadata = {"location": external_location}

    if event_type == ScheduledEventType.EXTERNAL:
        channel_id = None
        if external_location == MISSING:
            raise EventLocationNotProvided("Location is required for external events")

    payload = {
        "name": name,
        "description": description,
        "channel_id": channel_id,
        "entity_type": event_type,
        "scheduled_start_time": start_time.isoformat() if start_time else MISSING,
        "scheduled_end_time": end_time.isoformat() if end_time else MISSING,
        "status": status,
        "entity_metadata": entity_metadata,
        "privacy_level": privacy_level,
        "image": to_image_data(cover_image) if cover_image else MISSING,
    }
    await self._client.http.modify_scheduled_event(self._guild_id, self.id, payload, reason)

StickerTypes

Bases: IntEnum

Types of sticker.

Source code in naff/models/discord/sticker.py
19
20
21
22
23
24
25
class StickerTypes(IntEnum):
    """Types of sticker."""

    STANDARD = 1
    """An official sticker in a pack, part of Nitro or in a removed purchasable pack."""
    GUILD = 2
    """A sticker uploaded to a Boosted guild for the guild's members."""

STANDARD = 1 class-attribute

An official sticker in a pack, part of Nitro or in a removed purchasable pack.

GUILD = 2 class-attribute

A sticker uploaded to a Boosted guild for the guild's members.

StickerFormatTypes

Bases: IntEnum

File formats for stickers.

Source code in naff/models/discord/sticker.py
28
29
30
31
32
33
class StickerFormatTypes(IntEnum):
    """File formats for stickers."""

    PNG = 1
    APNG = 2
    LOTTIE = 3

StickerItem

Bases: DiscordObject

Source code in naff/models/discord/sticker.py
36
37
38
39
40
41
@define(kw_only=False)
class StickerItem(DiscordObject):
    name: str = field(repr=True)
    """Name of the sticker."""
    format_type: StickerFormatTypes = field(repr=True, converter=StickerFormatTypes)
    """Type of sticker image format."""

name: str = field(repr=True) class-attribute

Name of the sticker.

format_type: StickerFormatTypes = field(repr=True, converter=StickerFormatTypes) class-attribute

Type of sticker image format.

Sticker

Bases: StickerItem

Represents a sticker that can be sent in messages.

Source code in naff/models/discord/sticker.py
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
@define()
class Sticker(StickerItem):
    """Represents a sticker that can be sent in messages."""

    pack_id: Optional["Snowflake_Type"] = field(default=None, converter=optional(to_snowflake))
    """For standard stickers, id of the pack the sticker is from."""
    description: Optional[str] = field(default=None)
    """Description of the sticker."""
    tags: str = field()
    """autocomplete/suggestion tags for the sticker (max 200 characters)"""
    type: Union[StickerTypes, int] = field(converter=StickerTypes)
    """Type of sticker."""
    available: Optional[bool] = field(default=True)
    """Whether this guild sticker can be used, may be false due to loss of Server Boosts."""
    sort_value: Optional[int] = field(default=None)
    """The standard sticker's sort order within its pack."""

    _user_id: Optional["Snowflake_Type"] = field(default=None, converter=optional(to_snowflake))
    _guild_id: Optional["Snowflake_Type"] = field(default=None, converter=optional(to_snowflake))

    async def fetch_creator(self) -> "User":
        """
        Fetch the user who created this emoji.

        Returns:
            User object

        """
        return await self._client.cache.fetch_user(self._user_id)

    def get_creator(self) -> "User":
        """
        Get the user who created this emoji.

        Returns:
            User object

        """
        return self._client.cache.get_user(self._user_id)

    async def fetch_guild(self) -> "Guild":
        """
        Fetch the guild associated with this emoji.

        Returns:
            Guild object

        """
        return await self._client.cache.fetch_guild(self._guild_id)

    def get_guild(self) -> "Guild":
        """
        Get the guild associated with this emoji.

        Returns:
            Guild object

        """
        return self._client.cache.get_guild(self._guild_id)

    async def edit(
        self,
        name: Absent[Optional[str]] = MISSING,
        description: Absent[Optional[str]] = MISSING,
        tags: Absent[Optional[str]] = MISSING,
        reason: Absent[Optional[str]] = MISSING,
    ) -> "Sticker":
        """
        Edit a sticker.

        Args:
            name: New name of the sticker
            description: New description of the sticker
            tags: New tags of the sticker
            reason: Reason for the edit

        Returns:
            The updated sticker instance

        """
        if not self._guild_id:
            raise ValueError("You can only edit guild stickers.")

        payload = dict_filter_none({"name": name, "description": description, "tags": tags})
        sticker_data = await self._client.http.modify_guild_sticker(payload, self._guild_id, self.id, reason)
        return self.update_from_dict(sticker_data)

    async def delete(self, reason: Optional[str] = MISSING) -> None:
        """
        Delete a sticker.

        Args:
            reason: Reason for the deletion

        Raises:
            ValueError: If you attempt to delete a non-guild sticker

        """
        if not self._guild_id:
            raise ValueError("You can only delete guild stickers.")

        await self._client.http.delete_guild_sticker(self._guild_id, self.id, reason)

pack_id: Optional[Snowflake_Type] = field(default=None, converter=optional(to_snowflake)) class-attribute

For standard stickers, id of the pack the sticker is from.

description: Optional[str] = field(default=None) class-attribute

Description of the sticker.

tags: str = field() class-attribute

autocomplete/suggestion tags for the sticker (max 200 characters)

type: Union[StickerTypes, int] = field(converter=StickerTypes) class-attribute

Type of sticker.

available: Optional[bool] = field(default=True) class-attribute

Whether this guild sticker can be used, may be false due to loss of Server Boosts.

sort_value: Optional[int] = field(default=None) class-attribute

The standard sticker's sort order within its pack.

fetch_creator() async

Fetch the user who created this emoji.

Returns:

Type Description
User

User object

Source code in naff/models/discord/sticker.py
64
65
66
67
68
69
70
71
72
async def fetch_creator(self) -> "User":
    """
    Fetch the user who created this emoji.

    Returns:
        User object

    """
    return await self._client.cache.fetch_user(self._user_id)

get_creator()

Get the user who created this emoji.

Returns:

Type Description
User

User object

Source code in naff/models/discord/sticker.py
74
75
76
77
78
79
80
81
82
def get_creator(self) -> "User":
    """
    Get the user who created this emoji.

    Returns:
        User object

    """
    return self._client.cache.get_user(self._user_id)

fetch_guild() async

Fetch the guild associated with this emoji.

Returns:

Type Description
Guild

Guild object

Source code in naff/models/discord/sticker.py
84
85
86
87
88
89
90
91
92
async def fetch_guild(self) -> "Guild":
    """
    Fetch the guild associated with this emoji.

    Returns:
        Guild object

    """
    return await self._client.cache.fetch_guild(self._guild_id)

get_guild()

Get the guild associated with this emoji.

Returns:

Type Description
Guild

Guild object

Source code in naff/models/discord/sticker.py
 94
 95
 96
 97
 98
 99
100
101
102
def get_guild(self) -> "Guild":
    """
    Get the guild associated with this emoji.

    Returns:
        Guild object

    """
    return self._client.cache.get_guild(self._guild_id)

edit(name=MISSING, description=MISSING, tags=MISSING, reason=MISSING) async

Edit a sticker.

Parameters:

Name Type Description Default
name Absent[Optional[str]]

New name of the sticker

MISSING
description Absent[Optional[str]]

New description of the sticker

MISSING
tags Absent[Optional[str]]

New tags of the sticker

MISSING
reason Absent[Optional[str]]

Reason for the edit

MISSING

Returns:

Type Description
Sticker

The updated sticker instance

Source code in naff/models/discord/sticker.py
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
async def edit(
    self,
    name: Absent[Optional[str]] = MISSING,
    description: Absent[Optional[str]] = MISSING,
    tags: Absent[Optional[str]] = MISSING,
    reason: Absent[Optional[str]] = MISSING,
) -> "Sticker":
    """
    Edit a sticker.

    Args:
        name: New name of the sticker
        description: New description of the sticker
        tags: New tags of the sticker
        reason: Reason for the edit

    Returns:
        The updated sticker instance

    """
    if not self._guild_id:
        raise ValueError("You can only edit guild stickers.")

    payload = dict_filter_none({"name": name, "description": description, "tags": tags})
    sticker_data = await self._client.http.modify_guild_sticker(payload, self._guild_id, self.id, reason)
    return self.update_from_dict(sticker_data)

delete(reason=MISSING) async

Delete a sticker.

Parameters:

Name Type Description Default
reason Optional[str]

Reason for the deletion

MISSING

Raises:

Type Description
ValueError

If you attempt to delete a non-guild sticker

Source code in naff/models/discord/sticker.py
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
async def delete(self, reason: Optional[str] = MISSING) -> None:
    """
    Delete a sticker.

    Args:
        reason: Reason for the deletion

    Raises:
        ValueError: If you attempt to delete a non-guild sticker

    """
    if not self._guild_id:
        raise ValueError("You can only delete guild stickers.")

    await self._client.http.delete_guild_sticker(self._guild_id, self.id, reason)

StickerPack

Bases: DiscordObject

Represents a pack of standard stickers.

Source code in naff/models/discord/sticker.py
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
@define()
class StickerPack(DiscordObject):
    """Represents a pack of standard stickers."""

    stickers: List["Sticker"] = field(factory=list)
    """The stickers in the pack."""
    name: str = field(repr=True)
    """Name of the sticker pack."""
    sku_id: "Snowflake_Type" = field(repr=True)
    """id of the pack's SKU."""
    cover_sticker_id: Optional["Snowflake_Type"] = field(default=None)
    """id of a sticker in the pack which is shown as the pack's icon."""
    description: str = field()
    """Description of the sticker pack."""
    banner_asset_id: "Snowflake_Type" = field()  # TODO CDN Asset
    """id of the sticker pack's banner image."""

stickers: List[Sticker] = field(factory=list) class-attribute

The stickers in the pack.

name: str = field(repr=True) class-attribute

Name of the sticker pack.

sku_id: Snowflake_Type = field(repr=True) class-attribute

id of the pack's SKU.

cover_sticker_id: Optional[Snowflake_Type] = field(default=None) class-attribute

id of a sticker in the pack which is shown as the pack's icon.

description: str = field() class-attribute

Description of the sticker pack.

banner_asset_id: Snowflake_Type = field() class-attribute

id of the sticker pack's banner image.

WebhookTypes

Bases: IntEnum

Source code in naff/models/discord/webhooks.py
33
34
35
36
37
38
39
class WebhookTypes(IntEnum):
    INCOMING = 1
    """Incoming Webhooks can post messages to channels with a generated token"""
    CHANNEL_FOLLOWER = 2
    """Channel Follower Webhooks are internal webhooks used with Channel Following to post new messages into channels"""
    APPLICATION = 3
    """Application webhooks are webhooks used with Interactions"""

INCOMING = 1 class-attribute

Incoming Webhooks can post messages to channels with a generated token

CHANNEL_FOLLOWER = 2 class-attribute

Channel Follower Webhooks are internal webhooks used with Channel Following to post new messages into channels

APPLICATION = 3 class-attribute

Application webhooks are webhooks used with Interactions

Webhook

Bases: DiscordObject, SendMixin

Source code in naff/models/discord/webhooks.py
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
@define()
class Webhook(DiscordObject, SendMixin):
    type: WebhookTypes = field()
    """The type of webhook"""

    application_id: Optional["Snowflake_Type"] = field(default=None)
    """the bot/OAuth2 application that created this webhook"""

    guild_id: Optional["Snowflake_Type"] = field(default=None)
    """the guild id this webhook is for, if any"""
    channel_id: Optional["Snowflake_Type"] = field(default=None)
    """the channel id this webhook is for, if any"""
    user_id: Optional["Snowflake_Type"] = field(default=None)
    """the user this webhook was created by"""

    name: Optional[str] = field(default=None)
    """the default name of the webhook"""
    avatar: Optional[str] = field(default=None)
    """the default user avatar hash of the webhook"""
    token: str = field(default=MISSING)
    """the secure token of the webhook (returned for Incoming Webhooks)"""
    url: Optional[str] = field(default=None)
    """the url used for executing the webhook (returned by the webhooks OAuth2 flow)"""

    source_guild_id: Optional["Snowflake_Type"] = field(default=None)
    """the guild of the channel that this webhook is following (returned for Channel Follower Webhooks)"""
    source_channel_id: Optional["Snowflake_Type"] = field(default=None)
    """the channel that this webhook is following (returned for Channel Follower Webhooks)"""

    @classmethod
    def from_url(cls, url: str, client: "Client") -> "Webhook":
        """
        Webhook object from a URL.

        Args:
            client: The client to use to make the request.
            url: Webhook URL

        Returns:
            A Webhook object.

        """
        match = re.search(r"discord(?:app)?\.com/api/webhooks/(?P<id>[0-9]{17,})/(?P<token>[\w\-.]{60,68})", url)
        if match is None:
            raise ValueError("Invalid webhook URL given.")

        data: Dict[str, Any] = match.groupdict()
        data["type"] = WebhookTypes.INCOMING
        return cls.from_dict(data, client)

    @classmethod
    async def create(
        cls,
        client: "Client",
        channel: Union["Snowflake_Type", "TYPE_MESSAGEABLE_CHANNEL"],
        name: str,
        avatar: Absent["UPLOADABLE_TYPE"] = MISSING,
    ) -> "Webhook":
        """
        Create a webhook.

        Args:
            client: The bot's client
            channel: The channel to create the webhook in
            name: The name of the webhook
            avatar: An optional default avatar to use

        Returns:
            New webhook object

        Raises:
            ValueError: If you try to name the webhook "Clyde"

        """
        if name.lower() == "clyde":
            raise ValueError('Webhook names cannot be "Clyde"')

        if not isinstance(channel, (str, int)):
            channel = to_snowflake(channel)

        if avatar:
            avatar = to_image_data(avatar)

        data = await client.http.create_webhook(channel, name, avatar)

        new_cls = cls.from_dict(data, client)

        return new_cls

    @classmethod
    def _process_dict(cls, data: Dict[str, Any], client: "Client") -> Dict[str, Any]:
        if data.get("user"):
            user = client.cache.place_user_data(data.pop("user"))
            data["user_id"] = user.id
        return data

    async def edit(
        self,
        name: Absent[str] = MISSING,
        avatar: Absent["UPLOADABLE_TYPE"] = MISSING,
        channel_id: Absent["Snowflake_Type"] = MISSING,
    ) -> None:
        """
        Edit this webhook.

        Args:
            name: The default name of the webhook.
            avatar: The image for the default webhook avatar.
            channel_id: The new channel id this webhook should be moved to.

        Raises:
            ValueError: If you try to name the webhook "Clyde"

        """
        if name.lower() == "clyde":
            raise ValueError('Webhook names cannot be "Clyde"')

        data = await self._client.http.modify_webhook(
            self.id, name, to_image_data(avatar), to_optional_snowflake(channel_id), self.token
        )
        self.update_from_dict(data)

    async def delete(self) -> None:
        """Delete this webhook."""
        await self._client.http.delete_webhook(self.id, self.token)

    async def send(
        self,
        content: Optional[str] = None,
        embed: Optional[Union["Embed", dict]] = None,
        embeds: Optional[Union[List[Union["Embed", dict]], Union["Embed", dict]]] = None,
        components: Optional[
            Union[List[List[Union["BaseComponent", dict]]], List[Union["BaseComponent", dict]], "BaseComponent", dict]
        ] = None,
        stickers: Optional[Union[List[Union["Sticker", "Snowflake_Type"]], "Sticker", "Snowflake_Type"]] = None,
        allowed_mentions: Optional[Union["AllowedMentions", dict]] = None,
        reply_to: Optional[Union["MessageReference", "Message", dict, "Snowflake_Type"]] = None,
        files: Optional[Union["UPLOADABLE_TYPE", List["UPLOADABLE_TYPE"]]] = None,
        file: Optional["UPLOADABLE_TYPE"] = None,
        tts: bool = False,
        suppress_embeds: bool = False,
        flags: Optional[Union[int, "MessageFlags"]] = None,
        username: str = None,
        avatar_url: str = None,
        wait: bool = False,
        thread: "Snowflake_Type" = None,
        **kwargs,
    ) -> Optional["Message"]:
        """
        Send a message as this webhook.

        Args:
            content: Message text content.
            embeds: Embedded rich content (up to 6000 characters).
            embed: Embedded rich content (up to 6000 characters).
            components: The components to include with the message.
            stickers: IDs of up to 3 stickers in the server to send in the message.
            allowed_mentions: Allowed mentions for the message.
            reply_to: Message to reference, must be from the same channel.
            files: Files to send, the path, bytes or File() instance, defaults to None. You may have up to 10 files.
            file: Files to send, the path, bytes or File() instance, defaults to None. You may have up to 10 files.
            tts: Should this message use Text To Speech.
            suppress_embeds: Should embeds be suppressed on this send
            flags: Message flags to apply.
            username: The username to use
            avatar_url: The url of an image to use as the avatar
            wait: Waits for confirmation of delivery. Set this to True if you intend to edit the message
            thread: Send this webhook to a thread channel

        Returns:
            New message object that was sent if `wait` is set to True

        """
        if not self.token:
            raise ForeignWebhookException("You cannot send messages with a webhook without a token!")

        if not content and not (embeds or embed) and not (files or file) and not stickers:
            raise EmptyMessageException("You cannot send a message without any content, embeds, files, or stickers")

        if suppress_embeds:
            if isinstance(flags, int):
                flags = MessageFlags(flags)
            flags = flags | MessageFlags.SUPPRESS_EMBEDS

        message_payload = process_message_payload(
            content=content,
            embeds=embeds or embed,
            components=components,
            stickers=stickers,
            allowed_mentions=allowed_mentions,
            reply_to=reply_to,
            tts=tts,
            flags=flags,
            username=username,
            avatar_url=avatar_url,
            **kwargs,
        )

        message_data = await self._client.http.execute_webhook(
            self.id, self.token, message_payload, wait, to_optional_snowflake(thread), files=files or file
        )
        if message_data:
            return self._client.cache.place_message_data(message_data)

    async def edit_message(
        self,
        message: Union["Message", "Snowflake_Type"],
        content: Optional[str] = None,
        embeds: Optional[Union[List[Union["Embed", dict]], Union["Embed", dict]]] = None,
        components: Optional[
            Union[List[List[Union["BaseComponent", dict]]], List[Union["BaseComponent", dict]], "BaseComponent", dict]
        ] = None,
        stickers: Optional[Union[List[Union["Sticker", "Snowflake_Type"]], "Sticker", "Snowflake_Type"]] = None,
        allowed_mentions: Optional[Union["AllowedMentions", dict]] = None,
        reply_to: Optional[Union["MessageReference", "Message", dict, "Snowflake_Type"]] = None,
        files: Optional[Union["UPLOADABLE_TYPE", List["UPLOADABLE_TYPE"]]] = None,
        file: Optional["UPLOADABLE_TYPE"] = None,
        tts: bool = False,
        flags: Optional[Union[int, "MessageFlags"]] = None,
    ) -> Optional["Message"]:
        """
        Edit a message as this webhook.

        Args:
            message: Message to edit
            content: Message text content.
            embeds: Embedded rich content (up to 6000 characters).
            components: The components to include with the message.
            stickers: IDs of up to 3 stickers in the server to send in the message.
            allowed_mentions: Allowed mentions for the message.
            reply_to: Message to reference, must be from the same channel.
            files: Files to send, the path, bytes or File() instance, defaults to None. You may have up to 10 files.
            file: Files to send, the path, bytes or File() instance, defaults to None. You may have up to 10 files.
            tts: Should this message use Text To Speech.
            flags: Message flags to apply.

        Returns:
            Updated message object that was sent if `wait` is set to True

        """
        message_payload = process_message_payload(
            content=content,
            embeds=embeds,
            components=components,
            stickers=stickers,
            allowed_mentions=allowed_mentions,
            reply_to=reply_to,
            tts=tts,
            flags=flags,
        )
        msg_data = await self._client.http.edit_webhook_message(
            self.id, self.token, to_snowflake(message), message_payload, files=files or file
        )
        if msg_data:
            return self._client.cache.place_message_data(msg_data)

type: WebhookTypes = field() class-attribute

The type of webhook

application_id: Optional[Snowflake_Type] = field(default=None) class-attribute

the bot/OAuth2 application that created this webhook

guild_id: Optional[Snowflake_Type] = field(default=None) class-attribute

the guild id this webhook is for, if any

channel_id: Optional[Snowflake_Type] = field(default=None) class-attribute

the channel id this webhook is for, if any

user_id: Optional[Snowflake_Type] = field(default=None) class-attribute

the user this webhook was created by

name: Optional[str] = field(default=None) class-attribute

the default name of the webhook

avatar: Optional[str] = field(default=None) class-attribute

the default user avatar hash of the webhook

token: str = field(default=MISSING) class-attribute

the secure token of the webhook (returned for Incoming Webhooks)

url: Optional[str] = field(default=None) class-attribute

the url used for executing the webhook (returned by the webhooks OAuth2 flow)

source_guild_id: Optional[Snowflake_Type] = field(default=None) class-attribute

the guild of the channel that this webhook is following (returned for Channel Follower Webhooks)

source_channel_id: Optional[Snowflake_Type] = field(default=None) class-attribute

the channel that this webhook is following (returned for Channel Follower Webhooks)

from_url(url, client) classmethod

Webhook object from a URL.

Parameters:

Name Type Description Default
client Client

The client to use to make the request.

required
url str

Webhook URL

required

Returns:

Type Description
Webhook

A Webhook object.

Source code in naff/models/discord/webhooks.py
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
@classmethod
def from_url(cls, url: str, client: "Client") -> "Webhook":
    """
    Webhook object from a URL.

    Args:
        client: The client to use to make the request.
        url: Webhook URL

    Returns:
        A Webhook object.

    """
    match = re.search(r"discord(?:app)?\.com/api/webhooks/(?P<id>[0-9]{17,})/(?P<token>[\w\-.]{60,68})", url)
    if match is None:
        raise ValueError("Invalid webhook URL given.")

    data: Dict[str, Any] = match.groupdict()
    data["type"] = WebhookTypes.INCOMING
    return cls.from_dict(data, client)

create(client, channel, name, avatar=MISSING) classmethod async

Create a webhook.

Parameters:

Name Type Description Default
client Client

The bot's client

required
channel Union[Snowflake_Type, TYPE_MESSAGEABLE_CHANNEL]

The channel to create the webhook in

required
name str

The name of the webhook

required
avatar Absent[UPLOADABLE_TYPE]

An optional default avatar to use

MISSING

Returns:

Type Description
Webhook

New webhook object

Raises:

Type Description
ValueError

If you try to name the webhook "Clyde"

Source code in naff/models/discord/webhooks.py
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
@classmethod
async def create(
    cls,
    client: "Client",
    channel: Union["Snowflake_Type", "TYPE_MESSAGEABLE_CHANNEL"],
    name: str,
    avatar: Absent["UPLOADABLE_TYPE"] = MISSING,
) -> "Webhook":
    """
    Create a webhook.

    Args:
        client: The bot's client
        channel: The channel to create the webhook in
        name: The name of the webhook
        avatar: An optional default avatar to use

    Returns:
        New webhook object

    Raises:
        ValueError: If you try to name the webhook "Clyde"

    """
    if name.lower() == "clyde":
        raise ValueError('Webhook names cannot be "Clyde"')

    if not isinstance(channel, (str, int)):
        channel = to_snowflake(channel)

    if avatar:
        avatar = to_image_data(avatar)

    data = await client.http.create_webhook(channel, name, avatar)

    new_cls = cls.from_dict(data, client)

    return new_cls

edit(name=MISSING, avatar=MISSING, channel_id=MISSING) async

Edit this webhook.

Parameters:

Name Type Description Default
name Absent[str]

The default name of the webhook.

MISSING
avatar Absent[UPLOADABLE_TYPE]

The image for the default webhook avatar.

MISSING
channel_id Absent[Snowflake_Type]

The new channel id this webhook should be moved to.

MISSING

Raises:

Type Description
ValueError

If you try to name the webhook "Clyde"

Source code in naff/models/discord/webhooks.py
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
async def edit(
    self,
    name: Absent[str] = MISSING,
    avatar: Absent["UPLOADABLE_TYPE"] = MISSING,
    channel_id: Absent["Snowflake_Type"] = MISSING,
) -> None:
    """
    Edit this webhook.

    Args:
        name: The default name of the webhook.
        avatar: The image for the default webhook avatar.
        channel_id: The new channel id this webhook should be moved to.

    Raises:
        ValueError: If you try to name the webhook "Clyde"

    """
    if name.lower() == "clyde":
        raise ValueError('Webhook names cannot be "Clyde"')

    data = await self._client.http.modify_webhook(
        self.id, name, to_image_data(avatar), to_optional_snowflake(channel_id), self.token
    )
    self.update_from_dict(data)

delete() async

Delete this webhook.

Source code in naff/models/discord/webhooks.py
164
165
166
async def delete(self) -> None:
    """Delete this webhook."""
    await self._client.http.delete_webhook(self.id, self.token)

send(content=None, embed=None, embeds=None, components=None, stickers=None, allowed_mentions=None, reply_to=None, files=None, file=None, tts=False, suppress_embeds=False, flags=None, username=None, avatar_url=None, wait=False, thread=None, **kwargs) async

Send a message as this webhook.

Parameters:

Name Type Description Default
content Optional[str]

Message text content.

None
embeds Optional[Union[List[Union[Embed, dict]], Union[Embed, dict]]]

Embedded rich content (up to 6000 characters).

None
embed Optional[Union[Embed, dict]]

Embedded rich content (up to 6000 characters).

None
components Optional[Union[List[List[Union[BaseComponent, dict]]], List[Union[BaseComponent, dict]], BaseComponent, dict]]

The components to include with the message.

None
stickers Optional[Union[List[Union[Sticker, Snowflake_Type]], Sticker, Snowflake_Type]]

IDs of up to 3 stickers in the server to send in the message.

None
allowed_mentions Optional[Union[AllowedMentions, dict]]

Allowed mentions for the message.

None
reply_to Optional[Union[MessageReference, Message, dict, Snowflake_Type]]

Message to reference, must be from the same channel.

None
files Optional[Union[UPLOADABLE_TYPE, List[UPLOADABLE_TYPE]]]

Files to send, the path, bytes or File() instance, defaults to None. You may have up to 10 files.

None
file Optional[UPLOADABLE_TYPE]

Files to send, the path, bytes or File() instance, defaults to None. You may have up to 10 files.

None
tts bool

Should this message use Text To Speech.

False
suppress_embeds bool

Should embeds be suppressed on this send

False
flags Optional[Union[int, MessageFlags]]

Message flags to apply.

None
username str

The username to use

None
avatar_url str

The url of an image to use as the avatar

None
wait bool

Waits for confirmation of delivery. Set this to True if you intend to edit the message

False
thread Snowflake_Type

Send this webhook to a thread channel

None

Returns:

Type Description
Optional[Message]

New message object that was sent if wait is set to True

Source code in naff/models/discord/webhooks.py
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
async def send(
    self,
    content: Optional[str] = None,
    embed: Optional[Union["Embed", dict]] = None,
    embeds: Optional[Union[List[Union["Embed", dict]], Union["Embed", dict]]] = None,
    components: Optional[
        Union[List[List[Union["BaseComponent", dict]]], List[Union["BaseComponent", dict]], "BaseComponent", dict]
    ] = None,
    stickers: Optional[Union[List[Union["Sticker", "Snowflake_Type"]], "Sticker", "Snowflake_Type"]] = None,
    allowed_mentions: Optional[Union["AllowedMentions", dict]] = None,
    reply_to: Optional[Union["MessageReference", "Message", dict, "Snowflake_Type"]] = None,
    files: Optional[Union["UPLOADABLE_TYPE", List["UPLOADABLE_TYPE"]]] = None,
    file: Optional["UPLOADABLE_TYPE"] = None,
    tts: bool = False,
    suppress_embeds: bool = False,
    flags: Optional[Union[int, "MessageFlags"]] = None,
    username: str = None,
    avatar_url: str = None,
    wait: bool = False,
    thread: "Snowflake_Type" = None,
    **kwargs,
) -> Optional["Message"]:
    """
    Send a message as this webhook.

    Args:
        content: Message text content.
        embeds: Embedded rich content (up to 6000 characters).
        embed: Embedded rich content (up to 6000 characters).
        components: The components to include with the message.
        stickers: IDs of up to 3 stickers in the server to send in the message.
        allowed_mentions: Allowed mentions for the message.
        reply_to: Message to reference, must be from the same channel.
        files: Files to send, the path, bytes or File() instance, defaults to None. You may have up to 10 files.
        file: Files to send, the path, bytes or File() instance, defaults to None. You may have up to 10 files.
        tts: Should this message use Text To Speech.
        suppress_embeds: Should embeds be suppressed on this send
        flags: Message flags to apply.
        username: The username to use
        avatar_url: The url of an image to use as the avatar
        wait: Waits for confirmation of delivery. Set this to True if you intend to edit the message
        thread: Send this webhook to a thread channel

    Returns:
        New message object that was sent if `wait` is set to True

    """
    if not self.token:
        raise ForeignWebhookException("You cannot send messages with a webhook without a token!")

    if not content and not (embeds or embed) and not (files or file) and not stickers:
        raise EmptyMessageException("You cannot send a message without any content, embeds, files, or stickers")

    if suppress_embeds:
        if isinstance(flags, int):
            flags = MessageFlags(flags)
        flags = flags | MessageFlags.SUPPRESS_EMBEDS

    message_payload = process_message_payload(
        content=content,
        embeds=embeds or embed,
        components=components,
        stickers=stickers,
        allowed_mentions=allowed_mentions,
        reply_to=reply_to,
        tts=tts,
        flags=flags,
        username=username,
        avatar_url=avatar_url,
        **kwargs,
    )

    message_data = await self._client.http.execute_webhook(
        self.id, self.token, message_payload, wait, to_optional_snowflake(thread), files=files or file
    )
    if message_data:
        return self._client.cache.place_message_data(message_data)

edit_message(message, content=None, embeds=None, components=None, stickers=None, allowed_mentions=None, reply_to=None, files=None, file=None, tts=False, flags=None) async

Edit a message as this webhook.

Parameters:

Name Type Description Default
message Union[Message, Snowflake_Type]

Message to edit

required
content Optional[str]

Message text content.

None
embeds Optional[Union[List[Union[Embed, dict]], Union[Embed, dict]]]

Embedded rich content (up to 6000 characters).

None
components Optional[Union[List[List[Union[BaseComponent, dict]]], List[Union[BaseComponent, dict]], BaseComponent, dict]]

The components to include with the message.

None
stickers Optional[Union[List[Union[Sticker, Snowflake_Type]], Sticker, Snowflake_Type]]

IDs of up to 3 stickers in the server to send in the message.

None
allowed_mentions Optional[Union[AllowedMentions, dict]]

Allowed mentions for the message.

None
reply_to Optional[Union[MessageReference, Message, dict, Snowflake_Type]]

Message to reference, must be from the same channel.

None
files Optional[Union[UPLOADABLE_TYPE, List[UPLOADABLE_TYPE]]]

Files to send, the path, bytes or File() instance, defaults to None. You may have up to 10 files.

None
file Optional[UPLOADABLE_TYPE]

Files to send, the path, bytes or File() instance, defaults to None. You may have up to 10 files.

None
tts bool

Should this message use Text To Speech.

False
flags Optional[Union[int, MessageFlags]]

Message flags to apply.

None

Returns:

Type Description
Optional[Message]

Updated message object that was sent if wait is set to True

Source code in naff/models/discord/webhooks.py
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
async def edit_message(
    self,
    message: Union["Message", "Snowflake_Type"],
    content: Optional[str] = None,
    embeds: Optional[Union[List[Union["Embed", dict]], Union["Embed", dict]]] = None,
    components: Optional[
        Union[List[List[Union["BaseComponent", dict]]], List[Union["BaseComponent", dict]], "BaseComponent", dict]
    ] = None,
    stickers: Optional[Union[List[Union["Sticker", "Snowflake_Type"]], "Sticker", "Snowflake_Type"]] = None,
    allowed_mentions: Optional[Union["AllowedMentions", dict]] = None,
    reply_to: Optional[Union["MessageReference", "Message", dict, "Snowflake_Type"]] = None,
    files: Optional[Union["UPLOADABLE_TYPE", List["UPLOADABLE_TYPE"]]] = None,
    file: Optional["UPLOADABLE_TYPE"] = None,
    tts: bool = False,
    flags: Optional[Union[int, "MessageFlags"]] = None,
) -> Optional["Message"]:
    """
    Edit a message as this webhook.

    Args:
        message: Message to edit
        content: Message text content.
        embeds: Embedded rich content (up to 6000 characters).
        components: The components to include with the message.
        stickers: IDs of up to 3 stickers in the server to send in the message.
        allowed_mentions: Allowed mentions for the message.
        reply_to: Message to reference, must be from the same channel.
        files: Files to send, the path, bytes or File() instance, defaults to None. You may have up to 10 files.
        file: Files to send, the path, bytes or File() instance, defaults to None. You may have up to 10 files.
        tts: Should this message use Text To Speech.
        flags: Message flags to apply.

    Returns:
        Updated message object that was sent if `wait` is set to True

    """
    message_payload = process_message_payload(
        content=content,
        embeds=embeds,
        components=components,
        stickers=stickers,
        allowed_mentions=allowed_mentions,
        reply_to=reply_to,
        tts=tts,
        flags=flags,
    )
    msg_data = await self._client.http.edit_webhook_message(
        self.id, self.token, to_snowflake(message), message_payload, files=files or file
    )
    if msg_data:
        return self._client.cache.place_message_data(msg_data)

ChannelHistory

Bases: AsyncIterator

An async iterator for searching through a channel's history.

Attributes:

Name Type Description
channel BaseChannel

The channel to search through

limit BaseChannel

The maximum number of messages to return (set to 0 for no limit)

before Snowflake_Type

get messages before this message ID

after Snowflake_Type

get messages after this message ID

around Snowflake_Type

get messages "around" this message ID

Source code in naff/models/discord/channel.py
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
class ChannelHistory(AsyncIterator):
    """
    An async iterator for searching through a channel's history.

    Attributes:
        channel: The channel to search through
        limit: The maximum number of messages to return (set to 0 for no limit)
        before: get messages before this message ID
        after: get messages after this message ID
        around: get messages "around" this message ID

    """

    def __init__(self, channel: "BaseChannel", limit=50, before=None, after=None, around=None) -> None:
        self.channel: "BaseChannel" = channel
        self.before: Snowflake_Type = before
        self.after: Snowflake_Type = after
        self.around: Snowflake_Type = around
        super().__init__(limit)

    async def fetch(self) -> List["models.Message"]:
        """
        Fetch additional objects.

        Your implementation of this method *must* return a list of objects.
        If no more objects are available, raise QueueEmpty

        Returns:
            List of objects

        Raises:
              QueueEmpty: when no more objects are available.

        """
        if self.after:
            if not self.last:
                self.last = namedtuple("temp", "id")
                self.last.id = self.after
            messages = await self.channel.fetch_messages(limit=self.get_limit, after=self.last.id)
            messages.sort(key=lambda x: x.id)

        elif self.around:
            messages = await self.channel.fetch_messages(limit=self.get_limit, around=self.around)
            # todo: decide how getting *more* messages from `around` would work
            self._limit = 1  # stops history from getting more messages

        else:
            if self.before and not self.last:
                self.last = namedtuple("temp", "id")
                self.last.id = self.before

            messages = await self.channel.fetch_messages(limit=self.get_limit, before=self.last.id)
            messages.sort(key=lambda x: x.id, reverse=True)
        return messages

fetch() async

Fetch additional objects.

Your implementation of this method must return a list of objects. If no more objects are available, raise QueueEmpty

Returns:

Type Description
List[Message]

List of objects

Raises:

Type Description
QueueEmpty

when no more objects are available.

Source code in naff/models/discord/channel.py
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
async def fetch(self) -> List["models.Message"]:
    """
    Fetch additional objects.

    Your implementation of this method *must* return a list of objects.
    If no more objects are available, raise QueueEmpty

    Returns:
        List of objects

    Raises:
          QueueEmpty: when no more objects are available.

    """
    if self.after:
        if not self.last:
            self.last = namedtuple("temp", "id")
            self.last.id = self.after
        messages = await self.channel.fetch_messages(limit=self.get_limit, after=self.last.id)
        messages.sort(key=lambda x: x.id)

    elif self.around:
        messages = await self.channel.fetch_messages(limit=self.get_limit, around=self.around)
        # todo: decide how getting *more* messages from `around` would work
        self._limit = 1  # stops history from getting more messages

    else:
        if self.before and not self.last:
            self.last = namedtuple("temp", "id")
            self.last.id = self.before

        messages = await self.channel.fetch_messages(limit=self.get_limit, before=self.last.id)
        messages.sort(key=lambda x: x.id, reverse=True)
    return messages

PermissionOverwrite

Bases: SnowflakeObject, DictSerializationMixin

Channel Permissions Overwrite object.

Note

id here is not an attribute of the overwrite, it is the ID of the overwritten instance

Source code in naff/models/discord/channel.py
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
@define()
class PermissionOverwrite(SnowflakeObject, DictSerializationMixin):
    """
    Channel Permissions Overwrite object.

    !!! note
        `id` here is not an attribute of the overwrite, it is the ID of the overwritten instance

    """

    type: "OverwriteTypes" = field(repr=True, converter=OverwriteTypes)
    """Permission overwrite type (role or member)"""
    allow: Optional["Permissions"] = field(repr=True, converter=optional_c(Permissions), kw_only=True, default=None)
    """Permissions to allow"""
    deny: Optional["Permissions"] = field(repr=True, converter=optional_c(Permissions), kw_only=True, default=None)
    """Permissions to deny"""

    @classmethod
    def for_target(cls, target_type: Union["models.Role", "models.Member", "models.User"]) -> "PermissionOverwrite":
        """
        Create a PermissionOverwrite for a role or member.

        Args:
            target_type: The type of the target (role or member)

        Returns:
            PermissionOverwrite

        """
        if isinstance(target_type, models.Role):
            return cls(type=OverwriteTypes.ROLE, id=target_type.id)
        elif isinstance(target_type, (models.Member, models.User)):
            return cls(type=OverwriteTypes.MEMBER, id=target_type.id)
        else:
            raise TypeError("target_type must be a Role, Member or User")

    def add_allows(self, *perms: "Permissions") -> None:
        """
        Add permissions to allow.

        Args:
            *perms: Permissions to add

        """
        if not self.allow:
            self.allow = Permissions.NONE
        for perm in perms:
            self.allow |= perm

    def add_denies(self, *perms: "Permissions") -> None:
        """
        Add permissions to deny.

        Args:
            *perms: Permissions to add

        """
        if not self.deny:
            self.deny = Permissions.NONE
        for perm in perms:
            self.deny |= perm

type: OverwriteTypes = field(repr=True, converter=OverwriteTypes) class-attribute

Permission overwrite type (role or member)

allow: Optional[Permissions] = field(repr=True, converter=optional_c(Permissions), kw_only=True, default=None) class-attribute

Permissions to allow

deny: Optional[Permissions] = field(repr=True, converter=optional_c(Permissions), kw_only=True, default=None) class-attribute

Permissions to deny

for_target(target_type) classmethod

Create a PermissionOverwrite for a role or member.

Parameters:

Name Type Description Default
target_type Union[Role, Member, User]

The type of the target (role or member)

required

Returns:

Type Description
PermissionOverwrite

PermissionOverwrite

Source code in naff/models/discord/channel.py
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
@classmethod
def for_target(cls, target_type: Union["models.Role", "models.Member", "models.User"]) -> "PermissionOverwrite":
    """
    Create a PermissionOverwrite for a role or member.

    Args:
        target_type: The type of the target (role or member)

    Returns:
        PermissionOverwrite

    """
    if isinstance(target_type, models.Role):
        return cls(type=OverwriteTypes.ROLE, id=target_type.id)
    elif isinstance(target_type, (models.Member, models.User)):
        return cls(type=OverwriteTypes.MEMBER, id=target_type.id)
    else:
        raise TypeError("target_type must be a Role, Member or User")

add_allows(*perms)

Add permissions to allow.

Parameters:

Name Type Description Default
*perms Permissions

Permissions to add

()
Source code in naff/models/discord/channel.py
167
168
169
170
171
172
173
174
175
176
177
178
def add_allows(self, *perms: "Permissions") -> None:
    """
    Add permissions to allow.

    Args:
        *perms: Permissions to add

    """
    if not self.allow:
        self.allow = Permissions.NONE
    for perm in perms:
        self.allow |= perm

add_denies(*perms)

Add permissions to deny.

Parameters:

Name Type Description Default
*perms Permissions

Permissions to add

()
Source code in naff/models/discord/channel.py
180
181
182
183
184
185
186
187
188
189
190
191
def add_denies(self, *perms: "Permissions") -> None:
    """
    Add permissions to deny.

    Args:
        *perms: Permissions to add

    """
    if not self.deny:
        self.deny = Permissions.NONE
    for perm in perms:
        self.deny |= perm

MessageableMixin

Bases: SendMixin

Source code in naff/models/discord/channel.py
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
@define(slots=False)
class MessageableMixin(SendMixin):
    last_message_id: Optional[Snowflake_Type] = field(
        default=None
    )  # TODO May need to think of dynamically updating this.
    """The id of the last message sent in this channel (may not point to an existing or valid message)"""
    default_auto_archive_duration: int = field(default=AutoArchiveDuration.ONE_DAY)
    """Default duration that the clients (not the API) will use for newly created threads, in minutes, to automatically archive the thread after recent activity"""
    last_pin_timestamp: Optional["models.Timestamp"] = field(default=None, converter=optional_c(timestamp_converter))
    """When the last pinned message was pinned. This may be None when a message is not pinned."""

    async def _send_http_request(
        self, message_payload: Union[dict, "FormData"], files: list["UPLOADABLE_TYPE"] | None = None
    ) -> dict:
        return await self._client.http.create_message(message_payload, self.id, files=files)

    async def fetch_message(self, message_id: Snowflake_Type) -> Optional["models.Message"]:
        """
        Fetch a message from the channel.

        Args:
            message_id: ID of message to retrieve.

        Returns:
            The message object fetched. If the message is not found, returns None.

        """
        try:
            return await self._client.cache.fetch_message(self.id, message_id)
        except NotFound:
            return None

    def get_message(self, message_id: Snowflake_Type) -> "models.Message":
        """
        Get a message from the channel.

        Args:
            message_id: ID of message to retrieve.

        Returns:
            The message object fetched.

        """
        message_id = to_snowflake(message_id)
        message: "models.Message" = self._client.cache.get_message(self.id, message_id)
        return message

    def history(
        self,
        limit: int = 100,
        before: Snowflake_Type = None,
        after: Snowflake_Type = None,
        around: Snowflake_Type = None,
    ) -> ChannelHistory:
        """
        Get an async iterator for the history of this channel.

        Args:
            limit: The maximum number of messages to return (set to 0 for no limit)
            before: get messages before this message ID
            after: get messages after this message ID
            around: get messages "around" this message ID

        ??? Hint "Example Usage:"
            ```python
            async for message in channel.history(limit=0):
                if message.author.id == 174918559539920897:
                    print("Found author's message")
                    # ...
                    break
            ```
            or
            ```python
            history = channel.history(limit=250)
            # Flatten the async iterator into a list
            messages = await history.flatten()
            ```

        Returns:
            ChannelHistory (AsyncIterator)

        """
        return ChannelHistory(self, limit, before, after, around)

    async def fetch_messages(
        self,
        limit: int = 50,
        around: Snowflake_Type = MISSING,
        before: Snowflake_Type = MISSING,
        after: Snowflake_Type = MISSING,
    ) -> List["models.Message"]:
        """
        Fetch multiple messages from the channel.

        Args:
            limit: Max number of messages to return, default `50`, max `100`
            around: Message to get messages around
            before: Message to get messages before
            after: Message to get messages after

        Returns:
            A list of messages fetched.

        """
        if limit > 100:
            raise ValueError("You cannot fetch more than 100 messages at once.")

        if around:
            around = to_snowflake(around)
        elif before:
            before = to_snowflake(before)
        elif after:
            after = to_snowflake(after)

        messages_data = await self._client.http.get_channel_messages(
            self.id, limit, around=around, before=before, after=after
        )
        for m in messages_data:
            m["guild_id"] = self._guild_id

        return [self._client.cache.place_message_data(m) for m in messages_data]

    async def fetch_pinned_messages(self) -> List["models.Message"]:
        """
        Fetch pinned messages from the channel.

        Returns:
            A list of messages fetched.

        """
        messages_data = await self._client.http.get_pinned_messages(self.id)
        return [self._client.cache.place_message_data(message_data) for message_data in messages_data]

    async def delete_messages(
        self, messages: List[Union[Snowflake_Type, "models.Message"]], reason: Absent[Optional[str]] = MISSING
    ) -> None:
        """
        Bulk delete messages from channel.

        Args:
            messages: List of messages or message IDs to delete.
            reason: The reason for this action. Used for audit logs.

        """
        message_ids = [to_snowflake(message) for message in messages]
        # TODO Add check for min/max and duplicates.

        if len(message_ids) == 1:
            # bulk delete messages will throw a http error if only 1 message is passed
            await self.delete_message(message_ids[0], reason)
        else:
            await self._client.http.bulk_delete_messages(self.id, message_ids, reason)

    async def delete_message(self, message: Union[Snowflake_Type, "models.Message"], reason: str = None) -> None:
        """
        Delete a single message from a channel.

        Args:
            message: The message to delete
            reason: The reason for this action

        """
        message = to_snowflake(message)
        await self._client.http.delete_message(self.id, message, reason=reason)

    async def purge(
        self,
        deletion_limit: int = 50,
        search_limit: int = 100,
        predicate: Callable[["models.Message"], bool] = MISSING,
        avoid_loading_msg: bool = True,
        before: Optional[Snowflake_Type] = MISSING,
        after: Optional[Snowflake_Type] = MISSING,
        around: Optional[Snowflake_Type] = MISSING,
        reason: Absent[Optional[str]] = MISSING,
    ) -> int:
        """
        Bulk delete messages within a channel. If a `predicate` is provided, it will be used to determine which messages to delete, otherwise all messages will be deleted within the `deletion_limit`.

        ??? Hint "Example Usage:"
            ```python
            # this will delete the last 20 messages sent by a user with the given ID
            deleted = await channel.purge(deletion_limit=20, predicate=lambda m: m.author.id == 174918559539920897)
            await channel.send(f"{deleted} messages deleted")
            ```

        Args:
            deletion_limit: The target amount of messages to delete
            search_limit: How many messages to search through
            predicate: A function that returns True or False, and takes a message as an argument
            avoid_loading_msg: Should the bot attempt to avoid deleting its own loading messages (recommended enabled)
            before: Search messages before this ID
            after: Search messages after this ID
            around: Search messages around this ID
            reason: The reason for this deletion

        Returns:
            The total amount of messages deleted

        """
        if not predicate:

            def predicate(m) -> bool:
                return True  # noqa

        to_delete = []

        # 1209600 14 days ago in seconds, 1420070400000 is used to convert to snowflake
        fourteen_days_ago = int((time.time() - 1209600) * 1000.0 - DISCORD_EPOCH) << 22
        async for message in self.history(limit=search_limit, before=before, after=after, around=around):
            if deletion_limit != 0 and len(to_delete) == deletion_limit:
                break

            if not predicate(message):
                # fails predicate
                continue

            if avoid_loading_msg:
                if message._author_id == self._client.user.id and MessageFlags.LOADING in message.flags:
                    continue

            if message.id < fourteen_days_ago:
                # message is too old to be purged
                continue

            to_delete.append(message.id)

        count = len(to_delete)
        while len(to_delete):
            iteration = [to_delete.pop() for i in range(min(100, len(to_delete)))]
            await self.delete_messages(iteration, reason=reason)
        return count

    async def trigger_typing(self) -> None:
        """Trigger a typing animation in this channel."""
        await self._client.http.trigger_typing_indicator(self.id)

    @property
    def typing(self) -> Typing:
        """A context manager to send a typing state to a given channel as long as long as the wrapped operation takes."""
        return Typing(self)

last_message_id: Optional[Snowflake_Type] = field(default=None) class-attribute

The id of the last message sent in this channel (may not point to an existing or valid message)

default_auto_archive_duration: int = field(default=AutoArchiveDuration.ONE_DAY) class-attribute

Default duration that the clients (not the API) will use for newly created threads, in minutes, to automatically archive the thread after recent activity

last_pin_timestamp: Optional[models.Timestamp] = field(default=None, converter=optional_c(timestamp_converter)) class-attribute

When the last pinned message was pinned. This may be None when a message is not pinned.

fetch_message(message_id) async

Fetch a message from the channel.

Parameters:

Name Type Description Default
message_id Snowflake_Type

ID of message to retrieve.

required

Returns:

Type Description
Optional[Message]

The message object fetched. If the message is not found, returns None.

Source code in naff/models/discord/channel.py
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
async def fetch_message(self, message_id: Snowflake_Type) -> Optional["models.Message"]:
    """
    Fetch a message from the channel.

    Args:
        message_id: ID of message to retrieve.

    Returns:
        The message object fetched. If the message is not found, returns None.

    """
    try:
        return await self._client.cache.fetch_message(self.id, message_id)
    except NotFound:
        return None

get_message(message_id)

Get a message from the channel.

Parameters:

Name Type Description Default
message_id Snowflake_Type

ID of message to retrieve.

required

Returns:

Type Description
Message

The message object fetched.

Source code in naff/models/discord/channel.py
226
227
228
229
230
231
232
233
234
235
236
237
238
239
def get_message(self, message_id: Snowflake_Type) -> "models.Message":
    """
    Get a message from the channel.

    Args:
        message_id: ID of message to retrieve.

    Returns:
        The message object fetched.

    """
    message_id = to_snowflake(message_id)
    message: "models.Message" = self._client.cache.get_message(self.id, message_id)
    return message

history(limit=100, before=None, after=None, around=None)

Get an async iterator for the history of this channel.

Parameters:

Name Type Description Default
limit int

The maximum number of messages to return (set to 0 for no limit)

100
before Snowflake_Type

get messages before this message ID

None
after Snowflake_Type

get messages after this message ID

None
around Snowflake_Type

get messages "around" this message ID

None
Example Usage:

1
2
3
4
5
async for message in channel.history(limit=0):
    if message.author.id == 174918559539920897:
        print("Found author's message")
        # ...
        break
or
1
2
3
history = channel.history(limit=250)
# Flatten the async iterator into a list
messages = await history.flatten()

Returns:

Type Description
ChannelHistory

ChannelHistory (AsyncIterator)

Source code in naff/models/discord/channel.py
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
def history(
    self,
    limit: int = 100,
    before: Snowflake_Type = None,
    after: Snowflake_Type = None,
    around: Snowflake_Type = None,
) -> ChannelHistory:
    """
    Get an async iterator for the history of this channel.

    Args:
        limit: The maximum number of messages to return (set to 0 for no limit)
        before: get messages before this message ID
        after: get messages after this message ID
        around: get messages "around" this message ID

    ??? Hint "Example Usage:"
        ```python
        async for message in channel.history(limit=0):
            if message.author.id == 174918559539920897:
                print("Found author's message")
                # ...
                break
        ```
        or
        ```python
        history = channel.history(limit=250)
        # Flatten the async iterator into a list
        messages = await history.flatten()
        ```

    Returns:
        ChannelHistory (AsyncIterator)

    """
    return ChannelHistory(self, limit, before, after, around)

fetch_messages(limit=50, around=MISSING, before=MISSING, after=MISSING) async

Fetch multiple messages from the channel.

Parameters:

Name Type Description Default
limit int

Max number of messages to return, default 50, max 100

50
around Snowflake_Type

Message to get messages around

MISSING
before Snowflake_Type

Message to get messages before

MISSING
after Snowflake_Type

Message to get messages after

MISSING

Returns:

Type Description
List[Message]

A list of messages fetched.

Source code in naff/models/discord/channel.py
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
async def fetch_messages(
    self,
    limit: int = 50,
    around: Snowflake_Type = MISSING,
    before: Snowflake_Type = MISSING,
    after: Snowflake_Type = MISSING,
) -> List["models.Message"]:
    """
    Fetch multiple messages from the channel.

    Args:
        limit: Max number of messages to return, default `50`, max `100`
        around: Message to get messages around
        before: Message to get messages before
        after: Message to get messages after

    Returns:
        A list of messages fetched.

    """
    if limit > 100:
        raise ValueError("You cannot fetch more than 100 messages at once.")

    if around:
        around = to_snowflake(around)
    elif before:
        before = to_snowflake(before)
    elif after:
        after = to_snowflake(after)

    messages_data = await self._client.http.get_channel_messages(
        self.id, limit, around=around, before=before, after=after
    )
    for m in messages_data:
        m["guild_id"] = self._guild_id

    return [self._client.cache.place_message_data(m) for m in messages_data]

fetch_pinned_messages() async

Fetch pinned messages from the channel.

Returns:

Type Description
List[Message]

A list of messages fetched.

Source code in naff/models/discord/channel.py
316
317
318
319
320
321
322
323
324
325
async def fetch_pinned_messages(self) -> List["models.Message"]:
    """
    Fetch pinned messages from the channel.

    Returns:
        A list of messages fetched.

    """
    messages_data = await self._client.http.get_pinned_messages(self.id)
    return [self._client.cache.place_message_data(message_data) for message_data in messages_data]

delete_messages(messages, reason=MISSING) async

Bulk delete messages from channel.

Parameters:

Name Type Description Default
messages List[Union[Snowflake_Type, Message]]

List of messages or message IDs to delete.

required
reason Absent[Optional[str]]

The reason for this action. Used for audit logs.

MISSING
Source code in naff/models/discord/channel.py
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
async def delete_messages(
    self, messages: List[Union[Snowflake_Type, "models.Message"]], reason: Absent[Optional[str]] = MISSING
) -> None:
    """
    Bulk delete messages from channel.

    Args:
        messages: List of messages or message IDs to delete.
        reason: The reason for this action. Used for audit logs.

    """
    message_ids = [to_snowflake(message) for message in messages]
    # TODO Add check for min/max and duplicates.

    if len(message_ids) == 1:
        # bulk delete messages will throw a http error if only 1 message is passed
        await self.delete_message(message_ids[0], reason)
    else:
        await self._client.http.bulk_delete_messages(self.id, message_ids, reason)

delete_message(message, reason=None) async

Delete a single message from a channel.

Parameters:

Name Type Description Default
message Union[Snowflake_Type, Message]

The message to delete

required
reason str

The reason for this action

None
Source code in naff/models/discord/channel.py
347
348
349
350
351
352
353
354
355
356
357
async def delete_message(self, message: Union[Snowflake_Type, "models.Message"], reason: str = None) -> None:
    """
    Delete a single message from a channel.

    Args:
        message: The message to delete
        reason: The reason for this action

    """
    message = to_snowflake(message)
    await self._client.http.delete_message(self.id, message, reason=reason)

purge(deletion_limit=50, search_limit=100, predicate=MISSING, avoid_loading_msg=True, before=MISSING, after=MISSING, around=MISSING, reason=MISSING) async

Bulk delete messages within a channel. If a predicate is provided, it will be used to determine which messages to delete, otherwise all messages will be deleted within the deletion_limit.

Example Usage:
1
2
3
# this will delete the last 20 messages sent by a user with the given ID
deleted = await channel.purge(deletion_limit=20, predicate=lambda m: m.author.id == 174918559539920897)
await channel.send(f"{deleted} messages deleted")

Parameters:

Name Type Description Default
deletion_limit int

The target amount of messages to delete

50
search_limit int

How many messages to search through

100
predicate Callable[[Message], bool]

A function that returns True or False, and takes a message as an argument

MISSING
avoid_loading_msg bool

Should the bot attempt to avoid deleting its own loading messages (recommended enabled)

True
before Optional[Snowflake_Type]

Search messages before this ID

MISSING
after Optional[Snowflake_Type]

Search messages after this ID

MISSING
around Optional[Snowflake_Type]

Search messages around this ID

MISSING
reason Absent[Optional[str]]

The reason for this deletion

MISSING

Returns:

Type Description
int

The total amount of messages deleted

Source code in naff/models/discord/channel.py
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
async def purge(
    self,
    deletion_limit: int = 50,
    search_limit: int = 100,
    predicate: Callable[["models.Message"], bool] = MISSING,
    avoid_loading_msg: bool = True,
    before: Optional[Snowflake_Type] = MISSING,
    after: Optional[Snowflake_Type] = MISSING,
    around: Optional[Snowflake_Type] = MISSING,
    reason: Absent[Optional[str]] = MISSING,
) -> int:
    """
    Bulk delete messages within a channel. If a `predicate` is provided, it will be used to determine which messages to delete, otherwise all messages will be deleted within the `deletion_limit`.

    ??? Hint "Example Usage:"
        ```python
        # this will delete the last 20 messages sent by a user with the given ID
        deleted = await channel.purge(deletion_limit=20, predicate=lambda m: m.author.id == 174918559539920897)
        await channel.send(f"{deleted} messages deleted")
        ```

    Args:
        deletion_limit: The target amount of messages to delete
        search_limit: How many messages to search through
        predicate: A function that returns True or False, and takes a message as an argument
        avoid_loading_msg: Should the bot attempt to avoid deleting its own loading messages (recommended enabled)
        before: Search messages before this ID
        after: Search messages after this ID
        around: Search messages around this ID
        reason: The reason for this deletion

    Returns:
        The total amount of messages deleted

    """
    if not predicate:

        def predicate(m) -> bool:
            return True  # noqa

    to_delete = []

    # 1209600 14 days ago in seconds, 1420070400000 is used to convert to snowflake
    fourteen_days_ago = int((time.time() - 1209600) * 1000.0 - DISCORD_EPOCH) << 22
    async for message in self.history(limit=search_limit, before=before, after=after, around=around):
        if deletion_limit != 0 and len(to_delete) == deletion_limit:
            break

        if not predicate(message):
            # fails predicate
            continue

        if avoid_loading_msg:
            if message._author_id == self._client.user.id and MessageFlags.LOADING in message.flags:
                continue

        if message.id < fourteen_days_ago:
            # message is too old to be purged
            continue

        to_delete.append(message.id)

    count = len(to_delete)
    while len(to_delete):
        iteration = [to_delete.pop() for i in range(min(100, len(to_delete)))]
        await self.delete_messages(iteration, reason=reason)
    return count

trigger_typing() async

Trigger a typing animation in this channel.

Source code in naff/models/discord/channel.py
427
428
429
async def trigger_typing(self) -> None:
    """Trigger a typing animation in this channel."""
    await self._client.http.trigger_typing_indicator(self.id)

typing() property

A context manager to send a typing state to a given channel as long as long as the wrapped operation takes.

Source code in naff/models/discord/channel.py
431
432
433
434
@property
def typing(self) -> Typing:
    """A context manager to send a typing state to a given channel as long as long as the wrapped operation takes."""
    return Typing(self)

InvitableMixin

Source code in naff/models/discord/channel.py
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
@define(slots=False)
class InvitableMixin:
    async def create_invite(
        self,
        max_age: int = 86400,
        max_uses: int = 0,
        temporary: bool = False,
        unique: bool = False,
        target_type: Optional[InviteTargetTypes] = None,
        target_user: Optional[Union[Snowflake_Type, "models.User"]] = None,
        target_application: Optional[Union[Snowflake_Type, "models.Application"]] = None,
        reason: Optional[str] = None,
    ) -> "models.Invite":
        """
        Creates a new channel invite.

        Args:
            max_age: Max age of invite in seconds, default 86400 (24 hours).
            max_uses: Max uses of invite, default 0.
            temporary: Grants temporary membership, default False.
            unique: Invite is unique, default false.
            target_type: Target type for streams and embedded applications.
            target_user: Target User ID for Stream target type.
            target_application: Target Application ID for Embedded App target type.
            reason: The reason for creating this invite.

        Returns:
            Newly created Invite object.

        """
        if target_type:
            if target_type == InviteTargetTypes.STREAM and not target_user:
                raise ValueError("Stream target must include target user id.")
            elif target_type == InviteTargetTypes.EMBEDDED_APPLICATION and not target_application:
                raise ValueError("Embedded Application target must include target application id.")

        if target_user and target_application:
            raise ValueError("Invite target must be either User or Embedded Application, not both.")
        elif target_user:
            target_user = to_snowflake(target_user)
            target_type = InviteTargetTypes.STREAM
        elif target_application:
            target_application = to_snowflake(target_application)
            target_type = InviteTargetTypes.EMBEDDED_APPLICATION

        invite_data = await self._client.http.create_channel_invite(
            self.id, max_age, max_uses, temporary, unique, target_type, target_user, target_application, reason
        )
        return models.Invite.from_dict(invite_data, self._client)

    async def fetch_invites(self) -> List["models.Invite"]:
        """
        Fetches all invites (with invite metadata) for the channel.

        Returns:
            List of Invite objects.

        """
        invites_data = await self._client.http.get_channel_invites(self.id)
        return models.Invite.from_list(invites_data, self._client)

create_invite(max_age=86400, max_uses=0, temporary=False, unique=False, target_type=None, target_user=None, target_application=None, reason=None) async

Creates a new channel invite.

Parameters:

Name Type Description Default
max_age int

Max age of invite in seconds, default 86400 (24 hours).

86400
max_uses int

Max uses of invite, default 0.

0
temporary bool

Grants temporary membership, default False.

False
unique bool

Invite is unique, default false.

False
target_type Optional[InviteTargetTypes]

Target type for streams and embedded applications.

None
target_user Optional[Union[Snowflake_Type, User]]

Target User ID for Stream target type.

None
target_application Optional[Union[Snowflake_Type, Application]]

Target Application ID for Embedded App target type.

None
reason Optional[str]

The reason for creating this invite.

None

Returns:

Type Description
Invite

Newly created Invite object.

Source code in naff/models/discord/channel.py
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
async def create_invite(
    self,
    max_age: int = 86400,
    max_uses: int = 0,
    temporary: bool = False,
    unique: bool = False,
    target_type: Optional[InviteTargetTypes] = None,
    target_user: Optional[Union[Snowflake_Type, "models.User"]] = None,
    target_application: Optional[Union[Snowflake_Type, "models.Application"]] = None,
    reason: Optional[str] = None,
) -> "models.Invite":
    """
    Creates a new channel invite.

    Args:
        max_age: Max age of invite in seconds, default 86400 (24 hours).
        max_uses: Max uses of invite, default 0.
        temporary: Grants temporary membership, default False.
        unique: Invite is unique, default false.
        target_type: Target type for streams and embedded applications.
        target_user: Target User ID for Stream target type.
        target_application: Target Application ID for Embedded App target type.
        reason: The reason for creating this invite.

    Returns:
        Newly created Invite object.

    """
    if target_type:
        if target_type == InviteTargetTypes.STREAM and not target_user:
            raise ValueError("Stream target must include target user id.")
        elif target_type == InviteTargetTypes.EMBEDDED_APPLICATION and not target_application:
            raise ValueError("Embedded Application target must include target application id.")

    if target_user and target_application:
        raise ValueError("Invite target must be either User or Embedded Application, not both.")
    elif target_user:
        target_user = to_snowflake(target_user)
        target_type = InviteTargetTypes.STREAM
    elif target_application:
        target_application = to_snowflake(target_application)
        target_type = InviteTargetTypes.EMBEDDED_APPLICATION

    invite_data = await self._client.http.create_channel_invite(
        self.id, max_age, max_uses, temporary, unique, target_type, target_user, target_application, reason
    )
    return models.Invite.from_dict(invite_data, self._client)

fetch_invites() async

Fetches all invites (with invite metadata) for the channel.

Returns:

Type Description
List[Invite]

List of Invite objects.

Source code in naff/models/discord/channel.py
487
488
489
490
491
492
493
494
495
496
async def fetch_invites(self) -> List["models.Invite"]:
    """
    Fetches all invites (with invite metadata) for the channel.

    Returns:
        List of Invite objects.

    """
    invites_data = await self._client.http.get_channel_invites(self.id)
    return models.Invite.from_list(invites_data, self._client)

ThreadableMixin

Source code in naff/models/discord/channel.py
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
@define(slots=False)
class ThreadableMixin:
    async def create_thread(
        self,
        name: str,
        message: Absent[Snowflake_Type] = MISSING,
        thread_type: Absent[ChannelTypes] = MISSING,
        invitable: Absent[bool] = MISSING,
        auto_archive_duration: AutoArchiveDuration = AutoArchiveDuration.ONE_DAY,
        reason: Absent[str] = None,
    ) -> "TYPE_THREAD_CHANNEL":
        """
        Creates a new thread in this channel. If a message is provided, it will be used as the initial message.

        Args:
            name: 1-100 character thread name
            message: The message to connect this thread to. Required for news channel.
            thread_type: Is the thread private or public. Not applicable to news channel, it will always be GUILD_NEWS_THREAD.
            invitable: whether non-moderators can add other non-moderators to a thread. Only applicable when creating a private thread.
            auto_archive_duration: Time before the thread will be automatically archived. Note 3 day and 7 day archive durations require the server to be boosted.
            reason: The reason for creating this thread.

        Returns:
            The created thread, if successful

        """
        if self.type == ChannelTypes.GUILD_NEWS and not message:
            raise ValueError("News channel must include message to create thread from.")

        elif message and (thread_type or invitable):
            raise ValueError("Message cannot be used with thread_type or invitable.")

        elif thread_type != ChannelTypes.GUILD_PRIVATE_THREAD and invitable:
            raise ValueError("Invitable only applies to private threads.")

        thread_data = await self._client.http.create_thread(
            channel_id=self.id,
            name=name,
            thread_type=thread_type,
            invitable=invitable,
            auto_archive_duration=auto_archive_duration,
            message_id=to_optional_snowflake(message),
            reason=reason,
        )
        return self._client.cache.place_channel_data(thread_data)

    async def fetch_public_archived_threads(
        self, limit: int = None, before: Optional["models.Timestamp"] = None
    ) -> "models.ThreadList":
        """
        Get a `ThreadList` of archived **public** threads available in this channel.

        Args:
            limit: optional maximum number of threads to return
            before: Returns threads before this timestamp

        Returns:
            A `ThreadList` of archived threads.

        """
        threads_data = await self._client.http.list_public_archived_threads(
            channel_id=self.id, limit=limit, before=before
        )
        threads_data["id"] = self.id
        return models.ThreadList.from_dict(threads_data, self._client)

    async def fetch_private_archived_threads(
        self, limit: int = None, before: Optional["models.Timestamp"] = None
    ) -> "models.ThreadList":
        """
        Get a `ThreadList` of archived **private** threads available in this channel.

        Args:
            limit: optional maximum number of threads to return
            before: Returns threads before this timestamp

        Returns:
            A `ThreadList` of archived threads.

        """
        threads_data = await self._client.http.list_private_archived_threads(
            channel_id=self.id, limit=limit, before=before
        )
        threads_data["id"] = self.id
        return models.ThreadList.from_dict(threads_data, self._client)

    async def fetch_archived_threads(
        self, limit: int = None, before: Optional["models.Timestamp"] = None
    ) -> "models.ThreadList":
        """
        Get a `ThreadList` of archived threads available in this channel.

        Args:
            limit: optional maximum number of threads to return
            before: Returns threads before this timestamp

        Returns:
            A `ThreadList` of archived threads.

        """
        threads_data = await self._client.http.list_private_archived_threads(
            channel_id=self.id, limit=limit, before=before
        )
        threads_data.update(
            await self._client.http.list_public_archived_threads(channel_id=self.id, limit=limit, before=before)
        )
        threads_data["id"] = self.id
        return models.ThreadList.from_dict(threads_data, self._client)

    async def fetch_joined_private_archived_threads(
        self, limit: int = None, before: Optional["models.Timestamp"] = None
    ) -> "models.ThreadList":
        """
        Get a `ThreadList` of threads the bot is a participant of in this channel.

        Args:
            limit: optional maximum number of threads to return
            before: Returns threads before this timestamp

        Returns:
            A `ThreadList` of threads the bot is a participant of.

        """
        threads_data = await self._client.http.list_joined_private_archived_threads(
            channel_id=self.id, limit=limit, before=before
        )
        threads_data["id"] = self.id
        return models.ThreadList.from_dict(threads_data, self._client)

    async def fetch_active_threads(self) -> "models.ThreadList":
        """
        Gets all active threads in the channel, including public and private threads.

        Returns:
            A `ThreadList` of active threads.

        """
        threads_data = await self._client.http.list_active_threads(guild_id=self._guild_id)

        # delete the items where the channel_id does not match
        removed_thread_ids = []
        cleaned_threads_data_threads = []
        for thread in threads_data["threads"]:
            if thread["parent_id"] == str(self.id):
                cleaned_threads_data_threads.append(thread)
            else:
                removed_thread_ids.append(thread["id"])
        threads_data["threads"] = cleaned_threads_data_threads

        # delete the member data which is not needed
        cleaned_member_data_threads = []
        for thread_member in threads_data["members"]:
            if thread_member["id"] not in removed_thread_ids:
                cleaned_member_data_threads.append(thread_member)
        threads_data["members"] = cleaned_member_data_threads

        return models.ThreadList.from_dict(threads_data, self._client)

    async def fetch_all_threads(self) -> "models.ThreadList":
        """
        Gets all threads in the channel. Active and archived, including public and private threads.

        Returns:
            A `ThreadList` of all threads.

        """
        threads = await self.fetch_active_threads()

        # update that data with the archived threads
        archived_threads = await self.fetch_archived_threads()
        threads.threads.extend(archived_threads.threads)
        threads.members.extend(archived_threads.members)

        return threads

create_thread(name, message=MISSING, thread_type=MISSING, invitable=MISSING, auto_archive_duration=AutoArchiveDuration.ONE_DAY, reason=None) async

Creates a new thread in this channel. If a message is provided, it will be used as the initial message.

Parameters:

Name Type Description Default
name str

1-100 character thread name

required
message Absent[Snowflake_Type]

The message to connect this thread to. Required for news channel.

MISSING
thread_type Absent[ChannelTypes]

Is the thread private or public. Not applicable to news channel, it will always be GUILD_NEWS_THREAD.

MISSING
invitable Absent[bool]

whether non-moderators can add other non-moderators to a thread. Only applicable when creating a private thread.

MISSING
auto_archive_duration AutoArchiveDuration

Time before the thread will be automatically archived. Note 3 day and 7 day archive durations require the server to be boosted.

AutoArchiveDuration.ONE_DAY
reason Absent[str]

The reason for creating this thread.

None

Returns:

Type Description
TYPE_THREAD_CHANNEL

The created thread, if successful

Source code in naff/models/discord/channel.py
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
async def create_thread(
    self,
    name: str,
    message: Absent[Snowflake_Type] = MISSING,
    thread_type: Absent[ChannelTypes] = MISSING,
    invitable: Absent[bool] = MISSING,
    auto_archive_duration: AutoArchiveDuration = AutoArchiveDuration.ONE_DAY,
    reason: Absent[str] = None,
) -> "TYPE_THREAD_CHANNEL":
    """
    Creates a new thread in this channel. If a message is provided, it will be used as the initial message.

    Args:
        name: 1-100 character thread name
        message: The message to connect this thread to. Required for news channel.
        thread_type: Is the thread private or public. Not applicable to news channel, it will always be GUILD_NEWS_THREAD.
        invitable: whether non-moderators can add other non-moderators to a thread. Only applicable when creating a private thread.
        auto_archive_duration: Time before the thread will be automatically archived. Note 3 day and 7 day archive durations require the server to be boosted.
        reason: The reason for creating this thread.

    Returns:
        The created thread, if successful

    """
    if self.type == ChannelTypes.GUILD_NEWS and not message:
        raise ValueError("News channel must include message to create thread from.")

    elif message and (thread_type or invitable):
        raise ValueError("Message cannot be used with thread_type or invitable.")

    elif thread_type != ChannelTypes.GUILD_PRIVATE_THREAD and invitable:
        raise ValueError("Invitable only applies to private threads.")

    thread_data = await self._client.http.create_thread(
        channel_id=self.id,
        name=name,
        thread_type=thread_type,
        invitable=invitable,
        auto_archive_duration=auto_archive_duration,
        message_id=to_optional_snowflake(message),
        reason=reason,
    )
    return self._client.cache.place_channel_data(thread_data)

fetch_public_archived_threads(limit=None, before=None) async

Get a ThreadList of archived public threads available in this channel.

Parameters:

Name Type Description Default
limit int

optional maximum number of threads to return

None
before Optional[Timestamp]

Returns threads before this timestamp

None

Returns:

Type Description
ThreadList

A ThreadList of archived threads.

Source code in naff/models/discord/channel.py
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
async def fetch_public_archived_threads(
    self, limit: int = None, before: Optional["models.Timestamp"] = None
) -> "models.ThreadList":
    """
    Get a `ThreadList` of archived **public** threads available in this channel.

    Args:
        limit: optional maximum number of threads to return
        before: Returns threads before this timestamp

    Returns:
        A `ThreadList` of archived threads.

    """
    threads_data = await self._client.http.list_public_archived_threads(
        channel_id=self.id, limit=limit, before=before
    )
    threads_data["id"] = self.id
    return models.ThreadList.from_dict(threads_data, self._client)

fetch_private_archived_threads(limit=None, before=None) async

Get a ThreadList of archived private threads available in this channel.

Parameters:

Name Type Description Default
limit int

optional maximum number of threads to return

None
before Optional[Timestamp]

Returns threads before this timestamp

None

Returns:

Type Description
ThreadList

A ThreadList of archived threads.

Source code in naff/models/discord/channel.py
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
async def fetch_private_archived_threads(
    self, limit: int = None, before: Optional["models.Timestamp"] = None
) -> "models.ThreadList":
    """
    Get a `ThreadList` of archived **private** threads available in this channel.

    Args:
        limit: optional maximum number of threads to return
        before: Returns threads before this timestamp

    Returns:
        A `ThreadList` of archived threads.

    """
    threads_data = await self._client.http.list_private_archived_threads(
        channel_id=self.id, limit=limit, before=before
    )
    threads_data["id"] = self.id
    return models.ThreadList.from_dict(threads_data, self._client)

fetch_archived_threads(limit=None, before=None) async

Get a ThreadList of archived threads available in this channel.

Parameters:

Name Type Description Default
limit int

optional maximum number of threads to return

None
before Optional[Timestamp]

Returns threads before this timestamp

None

Returns:

Type Description
ThreadList

A ThreadList of archived threads.

Source code in naff/models/discord/channel.py
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
async def fetch_archived_threads(
    self, limit: int = None, before: Optional["models.Timestamp"] = None
) -> "models.ThreadList":
    """
    Get a `ThreadList` of archived threads available in this channel.

    Args:
        limit: optional maximum number of threads to return
        before: Returns threads before this timestamp

    Returns:
        A `ThreadList` of archived threads.

    """
    threads_data = await self._client.http.list_private_archived_threads(
        channel_id=self.id, limit=limit, before=before
    )
    threads_data.update(
        await self._client.http.list_public_archived_threads(channel_id=self.id, limit=limit, before=before)
    )
    threads_data["id"] = self.id
    return models.ThreadList.from_dict(threads_data, self._client)

fetch_joined_private_archived_threads(limit=None, before=None) async

Get a ThreadList of threads the bot is a participant of in this channel.

Parameters:

Name Type Description Default
limit int

optional maximum number of threads to return

None
before Optional[Timestamp]

Returns threads before this timestamp

None

Returns:

Type Description
ThreadList

A ThreadList of threads the bot is a participant of.

Source code in naff/models/discord/channel.py
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
async def fetch_joined_private_archived_threads(
    self, limit: int = None, before: Optional["models.Timestamp"] = None
) -> "models.ThreadList":
    """
    Get a `ThreadList` of threads the bot is a participant of in this channel.

    Args:
        limit: optional maximum number of threads to return
        before: Returns threads before this timestamp

    Returns:
        A `ThreadList` of threads the bot is a participant of.

    """
    threads_data = await self._client.http.list_joined_private_archived_threads(
        channel_id=self.id, limit=limit, before=before
    )
    threads_data["id"] = self.id
    return models.ThreadList.from_dict(threads_data, self._client)

fetch_active_threads() async

Gets all active threads in the channel, including public and private threads.

Returns:

Type Description
ThreadList

A ThreadList of active threads.

Source code in naff/models/discord/channel.py
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
async def fetch_active_threads(self) -> "models.ThreadList":
    """
    Gets all active threads in the channel, including public and private threads.

    Returns:
        A `ThreadList` of active threads.

    """
    threads_data = await self._client.http.list_active_threads(guild_id=self._guild_id)

    # delete the items where the channel_id does not match
    removed_thread_ids = []
    cleaned_threads_data_threads = []
    for thread in threads_data["threads"]:
        if thread["parent_id"] == str(self.id):
            cleaned_threads_data_threads.append(thread)
        else:
            removed_thread_ids.append(thread["id"])
    threads_data["threads"] = cleaned_threads_data_threads

    # delete the member data which is not needed
    cleaned_member_data_threads = []
    for thread_member in threads_data["members"]:
        if thread_member["id"] not in removed_thread_ids:
            cleaned_member_data_threads.append(thread_member)
    threads_data["members"] = cleaned_member_data_threads

    return models.ThreadList.from_dict(threads_data, self._client)

fetch_all_threads() async

Gets all threads in the channel. Active and archived, including public and private threads.

Returns:

Type Description
ThreadList

A ThreadList of all threads.

Source code in naff/models/discord/channel.py
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
async def fetch_all_threads(self) -> "models.ThreadList":
    """
    Gets all threads in the channel. Active and archived, including public and private threads.

    Returns:
        A `ThreadList` of all threads.

    """
    threads = await self.fetch_active_threads()

    # update that data with the archived threads
    archived_threads = await self.fetch_archived_threads()
    threads.threads.extend(archived_threads.threads)
    threads.members.extend(archived_threads.members)

    return threads

WebhookMixin

Source code in naff/models/discord/channel.py
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
@define(slots=False)
class WebhookMixin:
    async def create_webhook(self, name: str, avatar: Absent[UPLOADABLE_TYPE] = MISSING) -> "models.Webhook":
        """
        Create a webhook in this channel.

        Args:
            name: The name of the webhook
            avatar: An optional default avatar image to use

        Returns:
            The created webhook object

        Raises:
            ValueError: If you try to name the webhook "Clyde"

        """
        return await models.Webhook.create(self._client, self, name, avatar)  # type: ignore

    async def delete_webhook(self, webhook: "models.Webhook") -> None:
        """
        Delete a given webhook in this channel.

        Args:
            webhook: The webhook to delete

        """
        return await webhook.delete()

    async def fetch_webhooks(self) -> List["models.Webhook"]:
        """
        Fetches all the webhooks for this channel.

        Returns:
            List of webhook objects

        """
        resp = await self._client.http.get_channel_webhooks(self.id)
        return [models.Webhook.from_dict(d, self._client) for d in resp]

create_webhook(name, avatar=MISSING) async

Create a webhook in this channel.

Parameters:

Name Type Description Default
name str

The name of the webhook

required
avatar Absent[UPLOADABLE_TYPE]

An optional default avatar image to use

MISSING

Returns:

Type Description
Webhook

The created webhook object

Raises:

Type Description
ValueError

If you try to name the webhook "Clyde"

Source code in naff/models/discord/channel.py
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
async def create_webhook(self, name: str, avatar: Absent[UPLOADABLE_TYPE] = MISSING) -> "models.Webhook":
    """
    Create a webhook in this channel.

    Args:
        name: The name of the webhook
        avatar: An optional default avatar image to use

    Returns:
        The created webhook object

    Raises:
        ValueError: If you try to name the webhook "Clyde"

    """
    return await models.Webhook.create(self._client, self, name, avatar)  # type: ignore

delete_webhook(webhook) async

Delete a given webhook in this channel.

Parameters:

Name Type Description Default
webhook Webhook

The webhook to delete

required
Source code in naff/models/discord/channel.py
694
695
696
697
698
699
700
701
702
async def delete_webhook(self, webhook: "models.Webhook") -> None:
    """
    Delete a given webhook in this channel.

    Args:
        webhook: The webhook to delete

    """
    return await webhook.delete()

fetch_webhooks() async

Fetches all the webhooks for this channel.

Returns:

Type Description
List[Webhook]

List of webhook objects

Source code in naff/models/discord/channel.py
704
705
706
707
708
709
710
711
712
713
async def fetch_webhooks(self) -> List["models.Webhook"]:
    """
    Fetches all the webhooks for this channel.

    Returns:
        List of webhook objects

    """
    resp = await self._client.http.get_channel_webhooks(self.id)
    return [models.Webhook.from_dict(d, self._client) for d in resp]

BaseChannel

Bases: DiscordObject

Source code in naff/models/discord/channel.py
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
@define(slots=False)
class BaseChannel(DiscordObject):
    name: Optional[str] = field(repr=True, default=None)
    """The name of the channel (1-100 characters)"""
    type: Union[ChannelTypes, int] = field(repr=True, converter=ChannelTypes)
    """The channel topic (0-1024 characters)"""

    @classmethod
    def from_dict_factory(cls, data: dict, client: "Client") -> "TYPE_ALL_CHANNEL":
        """
        Creates a channel object of the appropriate type.

        Args:
            data: The channel data.
            client: The bot.

        Returns:
            The new channel object.

        """
        channel_type = data.get("type", None)
        channel_class = TYPE_CHANNEL_MAPPING.get(channel_type, None)
        if not channel_class:
            logger.error(f"Unsupported channel type for {data} ({channel_type}).")
            channel_class = BaseChannel

        return channel_class.from_dict(data, client)

    @property
    def mention(self) -> str:
        """Returns a string that would mention the channel."""
        return f"<#{self.id}>"

    async def edit(
        self,
        name: Absent[str] = MISSING,
        icon: Absent[UPLOADABLE_TYPE] = MISSING,
        type: Absent[ChannelTypes] = MISSING,
        position: Absent[int] = MISSING,
        topic: Absent[str] = MISSING,
        nsfw: Absent[bool] = MISSING,
        rate_limit_per_user: Absent[int] = MISSING,
        bitrate: Absent[int] = MISSING,
        user_limit: Absent[int] = MISSING,
        permission_overwrites: Absent[
            Union[dict, PermissionOverwrite, List[Union[dict, PermissionOverwrite]]]
        ] = MISSING,
        parent_id: Absent[Snowflake_Type] = MISSING,
        rtc_region: Absent[Union["models.VoiceRegion", str]] = MISSING,
        video_quality_mode: Absent[VideoQualityModes] = MISSING,
        default_auto_archive_duration: Absent[AutoArchiveDuration] = MISSING,
        archived: Absent[bool] = MISSING,
        auto_archive_duration: Absent[AutoArchiveDuration] = MISSING,
        locked: Absent[bool] = MISSING,
        invitable: Absent[bool] = MISSING,
        reason: Absent[str] = MISSING,
        **kwargs,
    ) -> "TYPE_ALL_CHANNEL":
        """
        Edits the channel.

        Args:
            name: 1-100 character channel name
            icon: DM Group icon
            type: The type of channel; only conversion between text and news is supported and only in guilds with the "NEWS" feature
            position: The position of the channel in the left-hand listing
            topic: 0-1024 character channel topic
            nsfw: Whether the channel is nsfw
            rate_limit_per_user: Amount of seconds a user has to wait before sending another message (0-21600)
            bitrate: The bitrate (in bits) of the voice channel; 8000 to 96000 (128000 for VIP servers)
            user_limit: The user limit of the voice channel; 0 refers to no limit, 1 to 99 refers to a user limit
            permission_overwrites: Channel or category-specific permissions
            parent_id: The id of the new parent category for a channel
            rtc_region: Channel voice region id, automatic when set to None.
            video_quality_mode: The camera video quality mode of the voice channel
            default_auto_archive_duration: The default duration that the clients use (not the API) for newly created threads in the channel, in minutes, to automatically archive the thread after recent activity
            archived: Whether the thread is archived
            auto_archive_duration: Duration in minutes to automatically archive the thread after recent activity, can be set to: 60, 1440, 4320, 10080
            locked: Whether the thread is locked; when a thread is locked, only users with MANAGE_THREADS can unarchive it
            invitable: Whether non-moderators can add other non-moderators to a thread; only available on private threads
            reason: The reason for editing the channel

        Returns:
            The edited channel. May be a new object if the channel type changes.

        """
        payload = {
            "name": name,
            "icon": to_image_data(icon),
            "type": type,
            "position": position,
            "topic": topic,
            "nsfw": nsfw,
            "rate_limit_per_user": rate_limit_per_user,
            "bitrate": bitrate,
            "user_limit": user_limit,
            "permission_overwrites": process_permission_overwrites(permission_overwrites),
            "parent_id": to_optional_snowflake(parent_id),
            "rtc_region": rtc_region.id if isinstance(rtc_region, models.VoiceRegion) else rtc_region,
            "video_quality_mode": video_quality_mode,
            "default_auto_archive_duration": default_auto_archive_duration,
            "archived": archived,
            "auto_archive_duration": auto_archive_duration,
            "locked": locked,
            "invitable": invitable,
            **kwargs,
        }
        channel_data = await self._client.http.modify_channel(self.id, payload, reason)
        if not channel_data:
            raise TooManyChanges(
                "You have changed this channel too frequently, you need to wait a while before trying again."
            ) from None

        return self._client.cache.place_channel_data(channel_data)

    async def delete(self, reason: Absent[Optional[str]] = MISSING) -> None:
        """
        Delete this channel.

        Args:
            reason: The reason for deleting this channel

        """
        await self._client.http.delete_channel(self.id, reason)

name: Optional[str] = field(repr=True, default=None) class-attribute

The name of the channel (1-100 characters)

type: Union[ChannelTypes, int] = field(repr=True, converter=ChannelTypes) class-attribute

The channel topic (0-1024 characters)

from_dict_factory(data, client) classmethod

Creates a channel object of the appropriate type.

Parameters:

Name Type Description Default
data dict

The channel data.

required
client Client

The bot.

required

Returns:

Type Description
TYPE_ALL_CHANNEL

The new channel object.

Source code in naff/models/discord/channel.py
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
@classmethod
def from_dict_factory(cls, data: dict, client: "Client") -> "TYPE_ALL_CHANNEL":
    """
    Creates a channel object of the appropriate type.

    Args:
        data: The channel data.
        client: The bot.

    Returns:
        The new channel object.

    """
    channel_type = data.get("type", None)
    channel_class = TYPE_CHANNEL_MAPPING.get(channel_type, None)
    if not channel_class:
        logger.error(f"Unsupported channel type for {data} ({channel_type}).")
        channel_class = BaseChannel

    return channel_class.from_dict(data, client)

mention() property

Returns a string that would mention the channel.

Source code in naff/models/discord/channel.py
744
745
746
747
@property
def mention(self) -> str:
    """Returns a string that would mention the channel."""
    return f"<#{self.id}>"

edit(name=MISSING, icon=MISSING, type=MISSING, position=MISSING, topic=MISSING, nsfw=MISSING, rate_limit_per_user=MISSING, bitrate=MISSING, user_limit=MISSING, permission_overwrites=MISSING, parent_id=MISSING, rtc_region=MISSING, video_quality_mode=MISSING, default_auto_archive_duration=MISSING, archived=MISSING, auto_archive_duration=MISSING, locked=MISSING, invitable=MISSING, reason=MISSING, **kwargs) async

Edits the channel.

Parameters:

Name Type Description Default
name Absent[str]

1-100 character channel name

MISSING
icon Absent[UPLOADABLE_TYPE]

DM Group icon

MISSING
type Absent[ChannelTypes]

The type of channel; only conversion between text and news is supported and only in guilds with the "NEWS" feature

MISSING
position Absent[int]

The position of the channel in the left-hand listing

MISSING
topic Absent[str]

0-1024 character channel topic

MISSING
nsfw Absent[bool]

Whether the channel is nsfw

MISSING
rate_limit_per_user Absent[int]

Amount of seconds a user has to wait before sending another message (0-21600)

MISSING
bitrate Absent[int]

The bitrate (in bits) of the voice channel; 8000 to 96000 (128000 for VIP servers)

MISSING
user_limit Absent[int]

The user limit of the voice channel; 0 refers to no limit, 1 to 99 refers to a user limit

MISSING
permission_overwrites Absent[Union[dict, PermissionOverwrite, List[Union[dict, PermissionOverwrite]]]]

Channel or category-specific permissions

MISSING
parent_id Absent[Snowflake_Type]

The id of the new parent category for a channel

MISSING
rtc_region Absent[Union[VoiceRegion, str]]

Channel voice region id, automatic when set to None.

MISSING
video_quality_mode Absent[VideoQualityModes]

The camera video quality mode of the voice channel

MISSING
default_auto_archive_duration Absent[AutoArchiveDuration]

The default duration that the clients use (not the API) for newly created threads in the channel, in minutes, to automatically archive the thread after recent activity

MISSING
archived Absent[bool]

Whether the thread is archived

MISSING
auto_archive_duration Absent[AutoArchiveDuration]

Duration in minutes to automatically archive the thread after recent activity, can be set to: 60, 1440, 4320, 10080

MISSING
locked Absent[bool]

Whether the thread is locked; when a thread is locked, only users with MANAGE_THREADS can unarchive it

MISSING
invitable Absent[bool]

Whether non-moderators can add other non-moderators to a thread; only available on private threads

MISSING
reason Absent[str]

The reason for editing the channel

MISSING

Returns:

Type Description
TYPE_ALL_CHANNEL

The edited channel. May be a new object if the channel type changes.

Source code in naff/models/discord/channel.py
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
async def edit(
    self,
    name: Absent[str] = MISSING,
    icon: Absent[UPLOADABLE_TYPE] = MISSING,
    type: Absent[ChannelTypes] = MISSING,
    position: Absent[int] = MISSING,
    topic: Absent[str] = MISSING,
    nsfw: Absent[bool] = MISSING,
    rate_limit_per_user: Absent[int] = MISSING,
    bitrate: Absent[int] = MISSING,
    user_limit: Absent[int] = MISSING,
    permission_overwrites: Absent[
        Union[dict, PermissionOverwrite, List[Union[dict, PermissionOverwrite]]]
    ] = MISSING,
    parent_id: Absent[Snowflake_Type] = MISSING,
    rtc_region: Absent[Union["models.VoiceRegion", str]] = MISSING,
    video_quality_mode: Absent[VideoQualityModes] = MISSING,
    default_auto_archive_duration: Absent[AutoArchiveDuration] = MISSING,
    archived: Absent[bool] = MISSING,
    auto_archive_duration: Absent[AutoArchiveDuration] = MISSING,
    locked: Absent[bool] = MISSING,
    invitable: Absent[bool] = MISSING,
    reason: Absent[str] = MISSING,
    **kwargs,
) -> "TYPE_ALL_CHANNEL":
    """
    Edits the channel.

    Args:
        name: 1-100 character channel name
        icon: DM Group icon
        type: The type of channel; only conversion between text and news is supported and only in guilds with the "NEWS" feature
        position: The position of the channel in the left-hand listing
        topic: 0-1024 character channel topic
        nsfw: Whether the channel is nsfw
        rate_limit_per_user: Amount of seconds a user has to wait before sending another message (0-21600)
        bitrate: The bitrate (in bits) of the voice channel; 8000 to 96000 (128000 for VIP servers)
        user_limit: The user limit of the voice channel; 0 refers to no limit, 1 to 99 refers to a user limit
        permission_overwrites: Channel or category-specific permissions
        parent_id: The id of the new parent category for a channel
        rtc_region: Channel voice region id, automatic when set to None.
        video_quality_mode: The camera video quality mode of the voice channel
        default_auto_archive_duration: The default duration that the clients use (not the API) for newly created threads in the channel, in minutes, to automatically archive the thread after recent activity
        archived: Whether the thread is archived
        auto_archive_duration: Duration in minutes to automatically archive the thread after recent activity, can be set to: 60, 1440, 4320, 10080
        locked: Whether the thread is locked; when a thread is locked, only users with MANAGE_THREADS can unarchive it
        invitable: Whether non-moderators can add other non-moderators to a thread; only available on private threads
        reason: The reason for editing the channel

    Returns:
        The edited channel. May be a new object if the channel type changes.

    """
    payload = {
        "name": name,
        "icon": to_image_data(icon),
        "type": type,
        "position": position,
        "topic": topic,
        "nsfw": nsfw,
        "rate_limit_per_user": rate_limit_per_user,
        "bitrate": bitrate,
        "user_limit": user_limit,
        "permission_overwrites": process_permission_overwrites(permission_overwrites),
        "parent_id": to_optional_snowflake(parent_id),
        "rtc_region": rtc_region.id if isinstance(rtc_region, models.VoiceRegion) else rtc_region,
        "video_quality_mode": video_quality_mode,
        "default_auto_archive_duration": default_auto_archive_duration,
        "archived": archived,
        "auto_archive_duration": auto_archive_duration,
        "locked": locked,
        "invitable": invitable,
        **kwargs,
    }
    channel_data = await self._client.http.modify_channel(self.id, payload, reason)
    if not channel_data:
        raise TooManyChanges(
            "You have changed this channel too frequently, you need to wait a while before trying again."
        ) from None

    return self._client.cache.place_channel_data(channel_data)

delete(reason=MISSING) async

Delete this channel.

Parameters:

Name Type Description Default
reason Absent[Optional[str]]

The reason for deleting this channel

MISSING
Source code in naff/models/discord/channel.py
831
832
833
834
835
836
837
838
839
async def delete(self, reason: Absent[Optional[str]] = MISSING) -> None:
    """
    Delete this channel.

    Args:
        reason: The reason for deleting this channel

    """
    await self._client.http.delete_channel(self.id, reason)

DMChannel

Bases: BaseChannel, MessageableMixin

Source code in naff/models/discord/channel.py
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
@define(slots=False)
class DMChannel(BaseChannel, MessageableMixin):
    recipients: List["models.User"] = field(factory=list)
    """The users of the DM that will receive messages sent"""

    @classmethod
    def _process_dict(cls, data: Dict[str, Any], client: "Client") -> Dict[str, Any]:
        data = super()._process_dict(data, client)
        if recipients := data.get("recipients", None):
            data["recipients"] = [
                client.cache.place_user_data(recipient) if isinstance(recipient, dict) else recipient
                for recipient in recipients
            ]
        return data

    @property
    def members(self) -> List["models.User"]:
        """Returns a list of users that are in this DM channel."""
        return self.recipients

recipients: List[models.User] = field(factory=list) class-attribute

The users of the DM that will receive messages sent

members() property

Returns a list of users that are in this DM channel.

Source code in naff/models/discord/channel.py
861
862
863
864
@property
def members(self) -> List["models.User"]:
    """Returns a list of users that are in this DM channel."""
    return self.recipients

DM

Bases: DMChannel

Source code in naff/models/discord/channel.py
867
868
869
870
871
872
@define()
class DM(DMChannel):
    @property
    def recipient(self) -> "models.User":
        """Returns the user that is in this DM channel."""
        return self.recipients[0]

recipient() property

Returns the user that is in this DM channel.

Source code in naff/models/discord/channel.py
869
870
871
872
@property
def recipient(self) -> "models.User":
    """Returns the user that is in this DM channel."""
    return self.recipients[0]

DMGroup

Bases: DMChannel

Source code in naff/models/discord/channel.py
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
@define()
class DMGroup(DMChannel):
    owner_id: Snowflake_Type = field(repr=True)
    """id of the creator of the group DM"""
    application_id: Optional[Snowflake_Type] = field(default=None)
    """Application id of the group DM creator if it is bot-created"""

    async def edit(
        self,
        name: Absent[str] = MISSING,
        icon: Absent[UPLOADABLE_TYPE] = MISSING,
        reason: Absent[str] = MISSING,
        **kwargs,
    ) -> "DMGroup":
        """
        Edit this DM Channel.

        Args:
            name: 1-100 character channel name
            icon: An icon to use
            reason: The reason for this change
        """
        return await super().edit(name=name, icon=icon, reason=reason, **kwargs)

    async def fetch_owner(self) -> "models.User":
        """Fetch the owner of this DM group"""
        return await self._client.cache.fetch_user(self.owner_id)

    def get_owner(self) -> "models.User":
        """Get the owner of this DM group"""
        return self._client.cache.get_user(self.owner_id)

    async def add_recipient(
        self, user: Union["models.User", Snowflake_Type], access_token: str, nickname: Absent[Optional[str]] = MISSING
    ) -> None:
        """
        Add a recipient to this DM Group.

        Args:
            user: The user to add
            access_token: access token of a user that has granted your app the gdm.join scope
            nickname: nickname to apply to the user being added

        """
        user = await self._client.cache.fetch_user(user)
        await self._client.http.group_dm_add_recipient(self.id, user.id, access_token, nickname)
        self.recipients.append(user)

    async def remove_recipient(self, user: Union["models.User", Snowflake_Type]) -> None:
        """
        Remove a recipient from this DM Group.

        Args:
            user: The user to remove

        """
        user = await self._client.cache.fetch_user(user)
        await self._client.http.group_dm_remove_recipient(self.id, user.id)
        self.recipients.remove(user)

owner_id: Snowflake_Type = field(repr=True) class-attribute

id of the creator of the group DM

application_id: Optional[Snowflake_Type] = field(default=None) class-attribute

Application id of the group DM creator if it is bot-created

edit(name=MISSING, icon=MISSING, reason=MISSING, **kwargs) async

Edit this DM Channel.

Parameters:

Name Type Description Default
name Absent[str]

1-100 character channel name

MISSING
icon Absent[UPLOADABLE_TYPE]

An icon to use

MISSING
reason Absent[str]

The reason for this change

MISSING
Source code in naff/models/discord/channel.py
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
async def edit(
    self,
    name: Absent[str] = MISSING,
    icon: Absent[UPLOADABLE_TYPE] = MISSING,
    reason: Absent[str] = MISSING,
    **kwargs,
) -> "DMGroup":
    """
    Edit this DM Channel.

    Args:
        name: 1-100 character channel name
        icon: An icon to use
        reason: The reason for this change
    """
    return await super().edit(name=name, icon=icon, reason=reason, **kwargs)

fetch_owner() async

Fetch the owner of this DM group

Source code in naff/models/discord/channel.py
899
900
901
async def fetch_owner(self) -> "models.User":
    """Fetch the owner of this DM group"""
    return await self._client.cache.fetch_user(self.owner_id)

get_owner()

Get the owner of this DM group

Source code in naff/models/discord/channel.py
903
904
905
def get_owner(self) -> "models.User":
    """Get the owner of this DM group"""
    return self._client.cache.get_user(self.owner_id)

add_recipient(user, access_token, nickname=MISSING) async

Add a recipient to this DM Group.

Parameters:

Name Type Description Default
user Union[User, Snowflake_Type]

The user to add

required
access_token str

access token of a user that has granted your app the gdm.join scope

required
nickname Absent[Optional[str]]

nickname to apply to the user being added

MISSING
Source code in naff/models/discord/channel.py
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
async def add_recipient(
    self, user: Union["models.User", Snowflake_Type], access_token: str, nickname: Absent[Optional[str]] = MISSING
) -> None:
    """
    Add a recipient to this DM Group.

    Args:
        user: The user to add
        access_token: access token of a user that has granted your app the gdm.join scope
        nickname: nickname to apply to the user being added

    """
    user = await self._client.cache.fetch_user(user)
    await self._client.http.group_dm_add_recipient(self.id, user.id, access_token, nickname)
    self.recipients.append(user)

remove_recipient(user) async

Remove a recipient from this DM Group.

Parameters:

Name Type Description Default
user Union[User, Snowflake_Type]

The user to remove

required
Source code in naff/models/discord/channel.py
923
924
925
926
927
928
929
930
931
932
933
async def remove_recipient(self, user: Union["models.User", Snowflake_Type]) -> None:
    """
    Remove a recipient from this DM Group.

    Args:
        user: The user to remove

    """
    user = await self._client.cache.fetch_user(user)
    await self._client.http.group_dm_remove_recipient(self.id, user.id)
    self.recipients.remove(user)

GuildChannel

Bases: BaseChannel

Source code in naff/models/discord/channel.py
 940
 941
 942
 943
 944
 945
 946
 947
 948
 949
 950
 951
 952
 953
 954
 955
 956
 957
 958
 959
 960
 961
 962
 963
 964
 965
 966
 967
 968
 969
 970
 971
 972
 973
 974
 975
 976
 977
 978
 979
 980
 981
 982
 983
 984
 985
 986
 987
 988
 989
 990
 991
 992
 993
 994
 995
 996
 997
 998
 999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
@define(slots=False)
class GuildChannel(BaseChannel):
    position: Optional[int] = field(default=0)
    """Sorting position of the channel"""
    nsfw: bool = field(default=False)
    """Whether the channel is nsfw"""
    parent_id: Optional[Snowflake_Type] = field(default=None, converter=optional_c(to_snowflake))
    """id of the parent category for a channel (each parent category can contain up to 50 channels)"""
    permission_overwrites: list[PermissionOverwrite] = field(factory=list)
    """A list of the overwritten permissions for the members and roles"""

    _guild_id: Optional[Snowflake_Type] = field(default=None, converter=optional_c(to_snowflake))

    @property
    def guild(self) -> "models.Guild":
        """The guild this channel belongs to."""
        return self._client.cache.get_guild(self._guild_id)

    @property
    def category(self) -> Optional["GuildCategory"]:
        """The parent category of this channel."""
        return self._client.cache.get_channel(self.parent_id)

    @property
    def gui_position(self) -> int:
        """The position of this channel in the Discord interface."""
        return self.guild.get_channel_gui_position(self.id)

    @classmethod
    def _process_dict(cls, data: Dict[str, Any], client: "Client") -> Dict[str, Any]:
        data = super()._process_dict(data, client)
        if overwrites := data.get("permission_overwrites"):
            data["permission_overwrites"] = PermissionOverwrite.from_list(overwrites)
        return data

    def permissions_for(self, instance: Snowflake_Type) -> Permissions:
        """
        Calculates permissions for an instance

        Args:
            instance: Member or Role instance (or its ID)

        Returns:
            Permissions data

        Raises:
            ValueError: If could not find any member or role by given ID
            RuntimeError: If given instance is from another guild

        """
        if (is_member := isinstance(instance, models.Member)) or isinstance(instance, models.Role):
            if instance._guild_id != self._guild_id:
                raise RuntimeError("Unable to calculate permissions for the instance from different guild")

            if is_member:
                return instance.channel_permissions(self)

            else:
                permissions = instance.permissions

                for overwrite in self.permission_overwrites:
                    if overwrite.id == instance.id:
                        permissions &= ~overwrite.deny
                        permissions |= overwrite.allow
                        break

                return permissions

        else:
            instance = to_snowflake(instance)
            guild = self.guild
            instance = guild.get_member(instance) or guild.get_role(instance)

            if not instance:
                raise ValueError("Unable to find any member or role by given instance ID")

            return self.permissions_for(instance)

    async def add_permission(
        self,
        target: Union["PermissionOverwrite", "models.Role", "models.User", "models.Member", "Snowflake_Type"],
        type: Optional["OverwriteTypes"] = None,
        allow: Optional[List["Permissions"] | int] = None,
        deny: Optional[List["Permissions"] | int] = None,
        reason: Optional[str] = None,
    ) -> None:
        """
        Add a permission to this channel.

        Args:
            target: The updated PermissionOverwrite object, or the Role or User object/id to update
            type: The type of permission overwrite. Only applicable if target is an id
            allow: List of permissions to allow. Only applicable if target is not an PermissionOverwrite object
            deny: List of permissions to deny. Only applicable if target is not an PermissionOverwrite object
            reason: The reason for this change

        Raises:
            ValueError: Invalid target for permission

        """
        allow = allow or []
        deny = deny or []
        if not isinstance(target, PermissionOverwrite):
            if isinstance(target, (models.User, models.Member)):
                target = target.id
                type = OverwriteTypes.MEMBER
            elif isinstance(target, models.Role):
                target = target.id
                type = OverwriteTypes.ROLE
            elif type and isinstance(target, Snowflake_Type):
                target = to_snowflake(target)
            else:
                raise ValueError("Invalid target and/or type for permission")
            overwrite = PermissionOverwrite(id=target, type=type, allow=Permissions.NONE, deny=Permissions.NONE)
            if isinstance(allow, int):
                overwrite.allow |= allow
            else:
                for perm in allow:
                    overwrite.allow |= perm
            if isinstance(deny, int):
                overwrite.deny |= deny
            else:
                for perm in deny:
                    overwrite.deny |= perm
        else:
            overwrite = target

        if exists := get(self.permission_overwrites, id=overwrite.id, type=overwrite.type):
            exists.deny = (exists.deny | overwrite.deny) & ~overwrite.allow
            exists.allow = (exists.allow | overwrite.allow) & ~overwrite.deny
            await self.edit_permission(exists, reason)
        else:
            permission_overwrites = self.permission_overwrites
            permission_overwrites.append(overwrite)
            await self.edit(permission_overwrites=permission_overwrites)

    async def edit_permission(self, overwrite: PermissionOverwrite, reason: Optional[str] = None) -> None:
        """
        Edit the permissions for this channel.

        Args:
            overwrite: The permission overwrite to apply
            reason: The reason for this change
        """
        await self._client.http.edit_channel_permission(
            self.id, overwrite.id, overwrite.allow, overwrite.deny, overwrite.type, reason
        )

    async def delete_permission(
        self,
        target: Union["PermissionOverwrite", "models.Role", "models.User"],
        reason: Absent[Optional[str]] = MISSING,
    ) -> None:
        """
        Delete a permission overwrite for this channel.

        Args:
            target: The permission overwrite to delete
            reason: The reason for this change

        """
        target = to_snowflake(target)
        await self._client.http.delete_channel_permission(self.id, target, reason)

    async def set_permission(
        self,
        target: Union["models.Role", "models.Member", "models.User"],
        *,
        add_reactions: bool | None = None,
        administrator: bool | None = None,
        attach_files: bool | None = None,
        ban_members: bool | None = None,
        change_nickname: bool | None = None,
        connect: bool | None = None,
        create_instant_invite: bool | None = None,
        deafen_members: bool | None = None,
        embed_links: bool | None = None,
        kick_members: bool | None = None,
        manage_channels: bool | None = None,
        manage_emojis_and_stickers: bool | None = None,
        manage_events: bool | None = None,
        manage_guild: bool | None = None,
        manage_messages: bool | None = None,
        manage_nicknames: bool | None = None,
        manage_roles: bool | None = None,
        manage_threads: bool | None = None,
        manage_webhooks: bool | None = None,
        mention_everyone: bool | None = None,
        moderate_members: bool | None = None,
        move_members: bool | None = None,
        mute_members: bool | None = None,
        priority_speaker: bool | None = None,
        read_message_history: bool | None = None,
        request_to_speak: bool | None = None,
        send_messages: bool | None = None,
        send_messages_in_threads: bool | None = None,
        send_tts_messages: bool | None = None,
        speak: bool | None = None,
        start_embedded_activities: bool | None = None,
        stream: bool | None = None,
        use_application_commands: bool | None = None,
        use_external_emojis: bool | None = None,
        use_external_stickers: bool | None = None,
        use_private_threads: bool | None = None,
        use_public_threads: bool | None = None,
        use_vad: bool | None = None,
        view_audit_log: bool | None = None,
        view_channel: bool | None = None,
        view_guild_insights: bool | None = None,
        reason: str = None,
    ) -> None:
        """
        Set the Permission Overwrites for a given target.

        Args:
            target: The target to set permission overwrites for
            add_reactions: Allows for the addition of reactions to messages
            administrator: Allows all permissions and bypasses channel permission overwrites
            attach_files: Allows for uploading images and files
            ban_members: Allows banning members
            change_nickname: Allows for modification of own nickname
            connect: Allows for joining of a voice channel
            create_instant_invite: Allows creation of instant invites
            deafen_members: Allows for deafening of members in a voice channel
            embed_links: Links sent by users with this permission will be auto-embedded
            kick_members: Allows kicking members
            manage_channels: Allows management and editing of channels
            manage_emojis_and_stickers: Allows management and editing of emojis and stickers
            manage_events: Allows for creating, editing, and deleting scheduled events
            manage_guild: Allows management and editing of the guild
            manage_messages: Allows for deletion of other users messages
            manage_nicknames: Allows for modification of other users nicknames
            manage_roles: Allows management and editing of roles
            manage_threads: Allows for deleting and archiving threads, and viewing all private threads
            manage_webhooks: Allows management and editing of webhooks
            mention_everyone: Allows for using the `@everyone` tag to notify all users in a channel, and the `@here` tag to notify all online users in a channel
            moderate_members: Allows for timing out users to prevent them from sending or reacting to messages in chat and threads, and from speaking in voice and stage channels
            move_members: Allows for moving of members between voice channels
            mute_members: Allows for muting members in a voice channel
            priority_speaker: Allows for using priority speaker in a voice channel
            read_message_history: Allows for reading of message history
            request_to_speak: Allows for requesting to speak in stage channels. (This permission is under active development and may be changed or removed.)
            send_messages:  Allows for sending messages in a channel (does not allow sending messages in threads)
            send_messages_in_threads: Allows for sending messages in threads
            send_tts_messages:  Allows for sending of `/tts` messages
            speak: Allows for speaking in a voice channel
            start_embedded_activities: Allows for using Activities (applications with the `EMBEDDED` flag) in a voice channel
            stream: Allows the user to go live
            use_application_commands: Allows members to use application commands, including slash commands and context menu commands
            use_external_emojis: Allows the usage of custom emojis from other servers
            use_external_stickers: Allows the usage of custom stickers from other servers
            use_private_threads: Allows for creating private threads
            use_public_threads:  Allows for creating public and announcement threads
            use_vad: Allows for using voice-activity-detection in a voice channel
            view_audit_log: Allows for viewing of audit logs
            view_channel: Allows guild members to view a channel, which includes reading messages in text channels and joining voice channels
            view_guild_insights: Allows for viewing guild insights
            reason: The reason for creating this overwrite
        """
        overwrite = PermissionOverwrite.for_target(target)

        allow: Permissions = Permissions.NONE
        deny: Permissions = Permissions.NONE

        for name, val in locals().items():
            if isinstance(val, bool):
                if val:
                    allow |= getattr(Permissions, name.upper())
                else:
                    deny |= getattr(Permissions, name.upper())

        overwrite.add_allows(allow)
        overwrite.add_denies(deny)

        await self.edit_permission(overwrite, reason)

    @property
    def members(self) -> List["models.Member"]:
        """Returns a list of members that can see this channel."""
        return [m for m in self.guild.members if Permissions.VIEW_CHANNEL in m.channel_permissions(self)]  # type: ignore

    @property
    def bots(self) -> List["models.Member"]:
        """Returns a list of bots that can see this channel."""
        return [m for m in self.guild.members if m.bot and Permissions.VIEW_CHANNEL in m.channel_permissions(self)]  # type: ignore

    @property
    def humans(self) -> List["models.Member"]:
        """Returns a list of humans that can see this channel."""
        return [m for m in self.guild.members if not m.bot and Permissions.VIEW_CHANNEL in m.channel_permissions(self)]  # type: ignore

    async def clone(self, name: Optional[str] = None, reason: Absent[Optional[str]] = MISSING) -> "TYPE_GUILD_CHANNEL":
        """
        Clone this channel and create a new one.

        Args:
            name: The name of the new channel. Defaults to the current name
            reason: The reason for creating this channel

        Returns:
            The newly created channel.

        """
        return await self.guild.create_channel(
            channel_type=self.type,
            name=name if name else self.name,
            topic=getattr(self, "topic", MISSING),
            position=self.position,
            permission_overwrites=self.permission_overwrites,
            category=self.category,
            nsfw=self.nsfw,
            bitrate=getattr(self, "bitrate", 64000),
            user_limit=getattr(self, "user_limit", 0),
            rate_limit_per_user=getattr(self, "rate_limit_per_user", 0),
            reason=reason,
        )

position: Optional[int] = field(default=0) class-attribute

Sorting position of the channel

nsfw: bool = field(default=False) class-attribute

Whether the channel is nsfw

parent_id: Optional[Snowflake_Type] = field(default=None, converter=optional_c(to_snowflake)) class-attribute

id of the parent category for a channel (each parent category can contain up to 50 channels)

permission_overwrites: list[PermissionOverwrite] = field(factory=list) class-attribute

A list of the overwritten permissions for the members and roles

guild() property

The guild this channel belongs to.

Source code in naff/models/discord/channel.py
953
954
955
956
@property
def guild(self) -> "models.Guild":
    """The guild this channel belongs to."""
    return self._client.cache.get_guild(self._guild_id)

category() property

The parent category of this channel.

Source code in naff/models/discord/channel.py
958
959
960
961
@property
def category(self) -> Optional["GuildCategory"]:
    """The parent category of this channel."""
    return self._client.cache.get_channel(self.parent_id)

gui_position() property

The position of this channel in the Discord interface.

Source code in naff/models/discord/channel.py
963
964
965
966
@property
def gui_position(self) -> int:
    """The position of this channel in the Discord interface."""
    return self.guild.get_channel_gui_position(self.id)

permissions_for(instance)

Calculates permissions for an instance

Parameters:

Name Type Description Default
instance Snowflake_Type

Member or Role instance (or its ID)

required

Returns:

Type Description
Permissions

Permissions data

Raises:

Type Description
ValueError

If could not find any member or role by given ID

RuntimeError

If given instance is from another guild

Source code in naff/models/discord/channel.py
 975
 976
 977
 978
 979
 980
 981
 982
 983
 984
 985
 986
 987
 988
 989
 990
 991
 992
 993
 994
 995
 996
 997
 998
 999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
def permissions_for(self, instance: Snowflake_Type) -> Permissions:
    """
    Calculates permissions for an instance

    Args:
        instance: Member or Role instance (or its ID)

    Returns:
        Permissions data

    Raises:
        ValueError: If could not find any member or role by given ID
        RuntimeError: If given instance is from another guild

    """
    if (is_member := isinstance(instance, models.Member)) or isinstance(instance, models.Role):
        if instance._guild_id != self._guild_id:
            raise RuntimeError("Unable to calculate permissions for the instance from different guild")

        if is_member:
            return instance.channel_permissions(self)

        else:
            permissions = instance.permissions

            for overwrite in self.permission_overwrites:
                if overwrite.id == instance.id:
                    permissions &= ~overwrite.deny
                    permissions |= overwrite.allow
                    break

            return permissions

    else:
        instance = to_snowflake(instance)
        guild = self.guild
        instance = guild.get_member(instance) or guild.get_role(instance)

        if not instance:
            raise ValueError("Unable to find any member or role by given instance ID")

        return self.permissions_for(instance)

add_permission(target, type=None, allow=None, deny=None, reason=None) async

Add a permission to this channel.

Parameters:

Name Type Description Default
target Union[PermissionOverwrite, Role, User, Member, Snowflake_Type]

The updated PermissionOverwrite object, or the Role or User object/id to update

required
type Optional[OverwriteTypes]

The type of permission overwrite. Only applicable if target is an id

None
allow Optional[List[Permissions] | int]

List of permissions to allow. Only applicable if target is not an PermissionOverwrite object

None
deny Optional[List[Permissions] | int]

List of permissions to deny. Only applicable if target is not an PermissionOverwrite object

None
reason Optional[str]

The reason for this change

None

Raises:

Type Description
ValueError

Invalid target for permission

Source code in naff/models/discord/channel.py
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
async def add_permission(
    self,
    target: Union["PermissionOverwrite", "models.Role", "models.User", "models.Member", "Snowflake_Type"],
    type: Optional["OverwriteTypes"] = None,
    allow: Optional[List["Permissions"] | int] = None,
    deny: Optional[List["Permissions"] | int] = None,
    reason: Optional[str] = None,
) -> None:
    """
    Add a permission to this channel.

    Args:
        target: The updated PermissionOverwrite object, or the Role or User object/id to update
        type: The type of permission overwrite. Only applicable if target is an id
        allow: List of permissions to allow. Only applicable if target is not an PermissionOverwrite object
        deny: List of permissions to deny. Only applicable if target is not an PermissionOverwrite object
        reason: The reason for this change

    Raises:
        ValueError: Invalid target for permission

    """
    allow = allow or []
    deny = deny or []
    if not isinstance(target, PermissionOverwrite):
        if isinstance(target, (models.User, models.Member)):
            target = target.id
            type = OverwriteTypes.MEMBER
        elif isinstance(target, models.Role):
            target = target.id
            type = OverwriteTypes.ROLE
        elif type and isinstance(target, Snowflake_Type):
            target = to_snowflake(target)
        else:
            raise ValueError("Invalid target and/or type for permission")
        overwrite = PermissionOverwrite(id=target, type=type, allow=Permissions.NONE, deny=Permissions.NONE)
        if isinstance(allow, int):
            overwrite.allow |= allow
        else:
            for perm in allow:
                overwrite.allow |= perm
        if isinstance(deny, int):
            overwrite.deny |= deny
        else:
            for perm in deny:
                overwrite.deny |= perm
    else:
        overwrite = target

    if exists := get(self.permission_overwrites, id=overwrite.id, type=overwrite.type):
        exists.deny = (exists.deny | overwrite.deny) & ~overwrite.allow
        exists.allow = (exists.allow | overwrite.allow) & ~overwrite.deny
        await self.edit_permission(exists, reason)
    else:
        permission_overwrites = self.permission_overwrites
        permission_overwrites.append(overwrite)
        await self.edit(permission_overwrites=permission_overwrites)

edit_permission(overwrite, reason=None) async

Edit the permissions for this channel.

Parameters:

Name Type Description Default
overwrite PermissionOverwrite

The permission overwrite to apply

required
reason Optional[str]

The reason for this change

None
Source code in naff/models/discord/channel.py
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
async def edit_permission(self, overwrite: PermissionOverwrite, reason: Optional[str] = None) -> None:
    """
    Edit the permissions for this channel.

    Args:
        overwrite: The permission overwrite to apply
        reason: The reason for this change
    """
    await self._client.http.edit_channel_permission(
        self.id, overwrite.id, overwrite.allow, overwrite.deny, overwrite.type, reason
    )

delete_permission(target, reason=MISSING) async

Delete a permission overwrite for this channel.

Parameters:

Name Type Description Default
target Union[PermissionOverwrite, Role, User]

The permission overwrite to delete

required
reason Absent[Optional[str]]

The reason for this change

MISSING
Source code in naff/models/discord/channel.py
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
async def delete_permission(
    self,
    target: Union["PermissionOverwrite", "models.Role", "models.User"],
    reason: Absent[Optional[str]] = MISSING,
) -> None:
    """
    Delete a permission overwrite for this channel.

    Args:
        target: The permission overwrite to delete
        reason: The reason for this change

    """
    target = to_snowflake(target)
    await self._client.http.delete_channel_permission(self.id, target, reason)

set_permission(target, *, add_reactions=None, administrator=None, attach_files=None, ban_members=None, change_nickname=None, connect=None, create_instant_invite=None, deafen_members=None, embed_links=None, kick_members=None, manage_channels=None, manage_emojis_and_stickers=None, manage_events=None, manage_guild=None, manage_messages=None, manage_nicknames=None, manage_roles=None, manage_threads=None, manage_webhooks=None, mention_everyone=None, moderate_members=None, move_members=None, mute_members=None, priority_speaker=None, read_message_history=None, request_to_speak=None, send_messages=None, send_messages_in_threads=None, send_tts_messages=None, speak=None, start_embedded_activities=None, stream=None, use_application_commands=None, use_external_emojis=None, use_external_stickers=None, use_private_threads=None, use_public_threads=None, use_vad=None, view_audit_log=None, view_channel=None, view_guild_insights=None, reason=None) async

Set the Permission Overwrites for a given target.

Parameters:

Name Type Description Default
target Union[Role, Member, User]

The target to set permission overwrites for

required
add_reactions bool | None

Allows for the addition of reactions to messages

None
administrator bool | None

Allows all permissions and bypasses channel permission overwrites

None
attach_files bool | None

Allows for uploading images and files

None
ban_members bool | None

Allows banning members

None
change_nickname bool | None

Allows for modification of own nickname

None
connect bool | None

Allows for joining of a voice channel

None
create_instant_invite bool | None

Allows creation of instant invites

None
deafen_members bool | None

Allows for deafening of members in a voice channel

None
embed_links bool | None

Links sent by users with this permission will be auto-embedded

None
kick_members bool | None

Allows kicking members

None
manage_channels bool | None

Allows management and editing of channels

None
manage_emojis_and_stickers bool | None

Allows management and editing of emojis and stickers

None
manage_events bool | None

Allows for creating, editing, and deleting scheduled events

None
manage_guild bool | None

Allows management and editing of the guild

None
manage_messages bool | None

Allows for deletion of other users messages

None
manage_nicknames bool | None

Allows for modification of other users nicknames

None
manage_roles bool | None

Allows management and editing of roles

None
manage_threads bool | None

Allows for deleting and archiving threads, and viewing all private threads

None
manage_webhooks bool | None

Allows management and editing of webhooks

None
mention_everyone bool | None

Allows for using the @everyone tag to notify all users in a channel, and the @here tag to notify all online users in a channel

None
moderate_members bool | None

Allows for timing out users to prevent them from sending or reacting to messages in chat and threads, and from speaking in voice and stage channels

None
move_members bool | None

Allows for moving of members between voice channels

None
mute_members bool | None

Allows for muting members in a voice channel

None
priority_speaker bool | None

Allows for using priority speaker in a voice channel

None
read_message_history bool | None

Allows for reading of message history

None
request_to_speak bool | None

Allows for requesting to speak in stage channels. (This permission is under active development and may be changed or removed.)

None
send_messages bool | None

Allows for sending messages in a channel (does not allow sending messages in threads)

None
send_messages_in_threads bool | None

Allows for sending messages in threads

None
send_tts_messages bool | None

Allows for sending of /tts messages

None
speak bool | None

Allows for speaking in a voice channel

None
start_embedded_activities bool | None

Allows for using Activities (applications with the EMBEDDED flag) in a voice channel

None
stream bool | None

Allows the user to go live

None
use_application_commands bool | None

Allows members to use application commands, including slash commands and context menu commands

None
use_external_emojis bool | None

Allows the usage of custom emojis from other servers

None
use_external_stickers bool | None

Allows the usage of custom stickers from other servers

None
use_private_threads bool | None

Allows for creating private threads

None
use_public_threads bool | None

Allows for creating public and announcement threads

None
use_vad bool | None

Allows for using voice-activity-detection in a voice channel

None
view_audit_log bool | None

Allows for viewing of audit logs

None
view_channel bool | None

Allows guild members to view a channel, which includes reading messages in text channels and joining voice channels

None
view_guild_insights bool | None

Allows for viewing guild insights

None
reason str

The reason for creating this overwrite

None
Source code in naff/models/discord/channel.py
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
async def set_permission(
    self,
    target: Union["models.Role", "models.Member", "models.User"],
    *,
    add_reactions: bool | None = None,
    administrator: bool | None = None,
    attach_files: bool | None = None,
    ban_members: bool | None = None,
    change_nickname: bool | None = None,
    connect: bool | None = None,
    create_instant_invite: bool | None = None,
    deafen_members: bool | None = None,
    embed_links: bool | None = None,
    kick_members: bool | None = None,
    manage_channels: bool | None = None,
    manage_emojis_and_stickers: bool | None = None,
    manage_events: bool | None = None,
    manage_guild: bool | None = None,
    manage_messages: bool | None = None,
    manage_nicknames: bool | None = None,
    manage_roles: bool | None = None,
    manage_threads: bool | None = None,
    manage_webhooks: bool | None = None,
    mention_everyone: bool | None = None,
    moderate_members: bool | None = None,
    move_members: bool | None = None,
    mute_members: bool | None = None,
    priority_speaker: bool | None = None,
    read_message_history: bool | None = None,
    request_to_speak: bool | None = None,
    send_messages: bool | None = None,
    send_messages_in_threads: bool | None = None,
    send_tts_messages: bool | None = None,
    speak: bool | None = None,
    start_embedded_activities: bool | None = None,
    stream: bool | None = None,
    use_application_commands: bool | None = None,
    use_external_emojis: bool | None = None,
    use_external_stickers: bool | None = None,
    use_private_threads: bool | None = None,
    use_public_threads: bool | None = None,
    use_vad: bool | None = None,
    view_audit_log: bool | None = None,
    view_channel: bool | None = None,
    view_guild_insights: bool | None = None,
    reason: str = None,
) -> None:
    """
    Set the Permission Overwrites for a given target.

    Args:
        target: The target to set permission overwrites for
        add_reactions: Allows for the addition of reactions to messages
        administrator: Allows all permissions and bypasses channel permission overwrites
        attach_files: Allows for uploading images and files
        ban_members: Allows banning members
        change_nickname: Allows for modification of own nickname
        connect: Allows for joining of a voice channel
        create_instant_invite: Allows creation of instant invites
        deafen_members: Allows for deafening of members in a voice channel
        embed_links: Links sent by users with this permission will be auto-embedded
        kick_members: Allows kicking members
        manage_channels: Allows management and editing of channels
        manage_emojis_and_stickers: Allows management and editing of emojis and stickers
        manage_events: Allows for creating, editing, and deleting scheduled events
        manage_guild: Allows management and editing of the guild
        manage_messages: Allows for deletion of other users messages
        manage_nicknames: Allows for modification of other users nicknames
        manage_roles: Allows management and editing of roles
        manage_threads: Allows for deleting and archiving threads, and viewing all private threads
        manage_webhooks: Allows management and editing of webhooks
        mention_everyone: Allows for using the `@everyone` tag to notify all users in a channel, and the `@here` tag to notify all online users in a channel
        moderate_members: Allows for timing out users to prevent them from sending or reacting to messages in chat and threads, and from speaking in voice and stage channels
        move_members: Allows for moving of members between voice channels
        mute_members: Allows for muting members in a voice channel
        priority_speaker: Allows for using priority speaker in a voice channel
        read_message_history: Allows for reading of message history
        request_to_speak: Allows for requesting to speak in stage channels. (This permission is under active development and may be changed or removed.)
        send_messages:  Allows for sending messages in a channel (does not allow sending messages in threads)
        send_messages_in_threads: Allows for sending messages in threads
        send_tts_messages:  Allows for sending of `/tts` messages
        speak: Allows for speaking in a voice channel
        start_embedded_activities: Allows for using Activities (applications with the `EMBEDDED` flag) in a voice channel
        stream: Allows the user to go live
        use_application_commands: Allows members to use application commands, including slash commands and context menu commands
        use_external_emojis: Allows the usage of custom emojis from other servers
        use_external_stickers: Allows the usage of custom stickers from other servers
        use_private_threads: Allows for creating private threads
        use_public_threads:  Allows for creating public and announcement threads
        use_vad: Allows for using voice-activity-detection in a voice channel
        view_audit_log: Allows for viewing of audit logs
        view_channel: Allows guild members to view a channel, which includes reading messages in text channels and joining voice channels
        view_guild_insights: Allows for viewing guild insights
        reason: The reason for creating this overwrite
    """
    overwrite = PermissionOverwrite.for_target(target)

    allow: Permissions = Permissions.NONE
    deny: Permissions = Permissions.NONE

    for name, val in locals().items():
        if isinstance(val, bool):
            if val:
                allow |= getattr(Permissions, name.upper())
            else:
                deny |= getattr(Permissions, name.upper())

    overwrite.add_allows(allow)
    overwrite.add_denies(deny)

    await self.edit_permission(overwrite, reason)

members() property

Returns a list of members that can see this channel.

Source code in naff/models/discord/channel.py
1216
1217
1218
1219
@property
def members(self) -> List["models.Member"]:
    """Returns a list of members that can see this channel."""
    return [m for m in self.guild.members if Permissions.VIEW_CHANNEL in m.channel_permissions(self)]  # type: ignore

bots() property

Returns a list of bots that can see this channel.

Source code in naff/models/discord/channel.py
1221
1222
1223
1224
@property
def bots(self) -> List["models.Member"]:
    """Returns a list of bots that can see this channel."""
    return [m for m in self.guild.members if m.bot and Permissions.VIEW_CHANNEL in m.channel_permissions(self)]  # type: ignore

humans() property

Returns a list of humans that can see this channel.

Source code in naff/models/discord/channel.py
1226
1227
1228
1229
@property
def humans(self) -> List["models.Member"]:
    """Returns a list of humans that can see this channel."""
    return [m for m in self.guild.members if not m.bot and Permissions.VIEW_CHANNEL in m.channel_permissions(self)]  # type: ignore

clone(name=None, reason=MISSING) async

Clone this channel and create a new one.

Parameters:

Name Type Description Default
name Optional[str]

The name of the new channel. Defaults to the current name

None
reason Absent[Optional[str]]

The reason for creating this channel

MISSING

Returns:

Type Description
TYPE_GUILD_CHANNEL

The newly created channel.

Source code in naff/models/discord/channel.py
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
async def clone(self, name: Optional[str] = None, reason: Absent[Optional[str]] = MISSING) -> "TYPE_GUILD_CHANNEL":
    """
    Clone this channel and create a new one.

    Args:
        name: The name of the new channel. Defaults to the current name
        reason: The reason for creating this channel

    Returns:
        The newly created channel.

    """
    return await self.guild.create_channel(
        channel_type=self.type,
        name=name if name else self.name,
        topic=getattr(self, "topic", MISSING),
        position=self.position,
        permission_overwrites=self.permission_overwrites,
        category=self.category,
        nsfw=self.nsfw,
        bitrate=getattr(self, "bitrate", 64000),
        user_limit=getattr(self, "user_limit", 0),
        rate_limit_per_user=getattr(self, "rate_limit_per_user", 0),
        reason=reason,
    )

GuildCategory

Bases: GuildChannel

Source code in naff/models/discord/channel.py
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
@define()
class GuildCategory(GuildChannel):
    @property
    def channels(self) -> List["TYPE_GUILD_CHANNEL"]:
        """Get all channels within the category"""
        return [channel for channel in self.guild.channels if channel.parent_id == self.id]

    @property
    def voice_channels(self) -> List["GuildVoice"]:
        """Get all voice channels within the category"""
        return [
            channel
            for channel in self.channels
            if isinstance(channel, GuildVoice) and not isinstance(channel, GuildStageVoice)
        ]

    @property
    def stage_channels(self) -> List["GuildStageVoice"]:
        """Get all stage channels within the category"""
        return [channel for channel in self.channels if isinstance(channel, GuildStageVoice)]

    @property
    def text_channels(self) -> List["GuildText"]:
        """Get all text channels within the category"""
        return [channel for channel in self.channels if isinstance(channel, GuildText)]

    @property
    def news_channels(self) -> List["GuildNews"]:
        """Get all news channels within the category"""
        return [channel for channel in self.channels if isinstance(channel, GuildNews)]

    async def edit(
        self,
        name: Absent[str] = MISSING,
        position: Absent[int] = MISSING,
        permission_overwrites: Absent[
            Union[dict, PermissionOverwrite, List[Union[dict, PermissionOverwrite]]]
        ] = MISSING,
        reason: Absent[str] = MISSING,
        **kwargs,
    ) -> "GuildCategory":
        """
        Edit this channel.

        Args:
            name: 1-100 character channel name
            position: the position of the channel in the left-hand listing
            permission_overwrites: channel or category-specific permissions
            reason: the reason for this change

        Returns:
            The updated channel object.

        """
        return await super().edit(
            name=name,
            position=position,
            permission_overwrites=permission_overwrites,
            reason=reason,
            **kwargs,
        )

    async def create_channel(
        self,
        channel_type: Union[ChannelTypes, int],
        name: str,
        topic: Absent[Optional[str]] = MISSING,
        position: Absent[Optional[int]] = MISSING,
        permission_overwrites: Absent[
            Union[dict, PermissionOverwrite, List[Union[dict, PermissionOverwrite]]]
        ] = MISSING,
        nsfw: bool = False,
        bitrate: int = 64000,
        user_limit: int = 0,
        rate_limit_per_user: int = 0,
        reason: Absent[Optional[str]] = MISSING,
    ) -> "TYPE_GUILD_CHANNEL":
        """
        Create a guild channel within this category, allows for explicit channel type setting.

        Args:
            channel_type: The type of channel to create
            name: The name of the channel
            topic: The topic of the channel
            position: The position of the channel in the channel list
            permission_overwrites: Permission overwrites to apply to the channel
            nsfw: Should this channel be marked nsfw
            bitrate: The bitrate of this channel, only for voice
            user_limit: The max users that can be in this channel, only for voice
            rate_limit_per_user: The time users must wait between sending messages
            reason: The reason for creating this channel

        Returns:
            The newly created channel.

        """
        return await self.guild.create_channel(
            channel_type=channel_type,
            name=name,
            topic=topic,
            position=position,
            permission_overwrites=permission_overwrites,
            category=self.id,
            nsfw=nsfw,
            bitrate=bitrate,
            user_limit=user_limit,
            rate_limit_per_user=rate_limit_per_user,
            reason=reason,
        )

    async def create_text_channel(
        self,
        name: str,
        topic: Absent[Optional[str]] = MISSING,
        position: Absent[Optional[int]] = MISSING,
        permission_overwrites: Absent[
            Union[dict, PermissionOverwrite, List[Union[dict, PermissionOverwrite]]]
        ] = MISSING,
        nsfw: bool = False,
        rate_limit_per_user: int = 0,
        reason: Absent[Optional[str]] = MISSING,
    ) -> "GuildText":
        """
        Create a text channel in this guild within this category.

        Args:
            name: The name of the channel
            topic: The topic of the channel
            position: The position of the channel in the channel list
            permission_overwrites: Permission overwrites to apply to the channel
            nsfw: Should this channel be marked nsfw
            rate_limit_per_user: The time users must wait between sending messages
            reason: The reason for creating this channel

        Returns:
           The newly created text channel.

        """
        return await self.create_channel(
            channel_type=ChannelTypes.GUILD_TEXT,
            name=name,
            topic=topic,
            position=position,
            permission_overwrites=permission_overwrites,
            nsfw=nsfw,
            rate_limit_per_user=rate_limit_per_user,
            reason=reason,
        )

    async def create_news_channel(
        self,
        name: str,
        topic: Absent[Optional[str]] = MISSING,
        position: Absent[Optional[int]] = MISSING,
        permission_overwrites: Absent[
            Union[dict, PermissionOverwrite, List[Union[dict, PermissionOverwrite]]]
        ] = MISSING,
        nsfw: bool = False,
        reason: Absent[Optional[str]] = MISSING,
    ) -> "GuildNews":
        """
        Create a news channel in this guild within this category.

        Args:
            name: The name of the channel
            topic: The topic of the channel
            position: The position of the channel in the channel list
            permission_overwrites: Permission overwrites to apply to the channel
            nsfw: Should this channel be marked nsfw
            reason: The reason for creating this channel

        Returns:
           The newly created news channel.

        """
        return await self.create_channel(
            channel_type=ChannelTypes.GUILD_NEWS,
            name=name,
            topic=topic,
            position=position,
            permission_overwrites=permission_overwrites,
            nsfw=nsfw,
            reason=reason,
        )

    async def create_voice_channel(
        self,
        name: str,
        topic: Absent[Optional[str]] = MISSING,
        position: Absent[Optional[int]] = MISSING,
        permission_overwrites: Absent[
            Union[dict, PermissionOverwrite, List[Union[dict, PermissionOverwrite]]]
        ] = MISSING,
        nsfw: bool = False,
        bitrate: int = 64000,
        user_limit: int = 0,
        reason: Absent[Optional[str]] = MISSING,
    ) -> "GuildVoice":
        """
        Create a guild voice channel within this category.

        Args:
            name: The name of the channel
            topic: The topic of the channel
            position: The position of the channel in the channel list
            permission_overwrites: Permission overwrites to apply to the channel
            nsfw: Should this channel be marked nsfw
            bitrate: The bitrate of this channel, only for voice
            user_limit: The max users that can be in this channel, only for voice
            reason: The reason for creating this channel

        Returns:
           The newly created voice channel.

        """
        return await self.create_channel(
            channel_type=ChannelTypes.GUILD_VOICE,
            name=name,
            topic=topic,
            position=position,
            permission_overwrites=permission_overwrites,
            nsfw=nsfw,
            bitrate=bitrate,
            user_limit=user_limit,
            reason=reason,
        )

    async def create_stage_channel(
        self,
        name: str,
        topic: Absent[Optional[str]] = MISSING,
        position: Absent[Optional[int]] = MISSING,
        permission_overwrites: Absent[
            Union[dict, PermissionOverwrite, List[Union[dict, PermissionOverwrite]]]
        ] = MISSING,
        bitrate: int = 64000,
        user_limit: int = 0,
        reason: Absent[Optional[str]] = MISSING,
    ) -> "GuildStageVoice":
        """
        Create a guild stage channel within this category.

        Args:
            name: The name of the channel
            topic: The topic of the channel
            position: The position of the channel in the channel list
            permission_overwrites: Permission overwrites to apply to the channel
            bitrate: The bitrate of this channel, only for voice
            user_limit: The max users that can be in this channel, only for voice
            reason: The reason for creating this channel

        Returns:
            The newly created stage channel.

        """
        return await self.create_channel(
            channel_type=ChannelTypes.GUILD_STAGE_VOICE,
            name=name,
            topic=topic,
            position=position,
            permission_overwrites=permission_overwrites,
            bitrate=bitrate,
            user_limit=user_limit,
            reason=reason,
        )

channels() property

Get all channels within the category

Source code in naff/models/discord/channel.py
1260
1261
1262
1263
@property
def channels(self) -> List["TYPE_GUILD_CHANNEL"]:
    """Get all channels within the category"""
    return [channel for channel in self.guild.channels if channel.parent_id == self.id]

voice_channels() property

Get all voice channels within the category

Source code in naff/models/discord/channel.py
1265
1266
1267
1268
1269
1270
1271
1272
@property
def voice_channels(self) -> List["GuildVoice"]:
    """Get all voice channels within the category"""
    return [
        channel
        for channel in self.channels
        if isinstance(channel, GuildVoice) and not isinstance(channel, GuildStageVoice)
    ]

stage_channels() property

Get all stage channels within the category

Source code in naff/models/discord/channel.py
1274
1275
1276
1277
@property
def stage_channels(self) -> List["GuildStageVoice"]:
    """Get all stage channels within the category"""
    return [channel for channel in self.channels if isinstance(channel, GuildStageVoice)]

text_channels() property

Get all text channels within the category

Source code in naff/models/discord/channel.py
1279
1280
1281
1282
@property
def text_channels(self) -> List["GuildText"]:
    """Get all text channels within the category"""
    return [channel for channel in self.channels if isinstance(channel, GuildText)]

news_channels() property

Get all news channels within the category

Source code in naff/models/discord/channel.py
1284
1285
1286
1287
@property
def news_channels(self) -> List["GuildNews"]:
    """Get all news channels within the category"""
    return [channel for channel in self.channels if isinstance(channel, GuildNews)]

edit(name=MISSING, position=MISSING, permission_overwrites=MISSING, reason=MISSING, **kwargs) async

Edit this channel.

Parameters:

Name Type Description Default
name Absent[str]

1-100 character channel name

MISSING
position Absent[int]

the position of the channel in the left-hand listing

MISSING
permission_overwrites Absent[Union[dict, PermissionOverwrite, List[Union[dict, PermissionOverwrite]]]]

channel or category-specific permissions

MISSING
reason Absent[str]

the reason for this change

MISSING

Returns:

Type Description
GuildCategory

The updated channel object.

Source code in naff/models/discord/channel.py
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
async def edit(
    self,
    name: Absent[str] = MISSING,
    position: Absent[int] = MISSING,
    permission_overwrites: Absent[
        Union[dict, PermissionOverwrite, List[Union[dict, PermissionOverwrite]]]
    ] = MISSING,
    reason: Absent[str] = MISSING,
    **kwargs,
) -> "GuildCategory":
    """
    Edit this channel.

    Args:
        name: 1-100 character channel name
        position: the position of the channel in the left-hand listing
        permission_overwrites: channel or category-specific permissions
        reason: the reason for this change

    Returns:
        The updated channel object.

    """
    return await super().edit(
        name=name,
        position=position,
        permission_overwrites=permission_overwrites,
        reason=reason,
        **kwargs,
    )

create_channel(channel_type, name, topic=MISSING, position=MISSING, permission_overwrites=MISSING, nsfw=False, bitrate=64000, user_limit=0, rate_limit_per_user=0, reason=MISSING) async

Create a guild channel within this category, allows for explicit channel type setting.

Parameters:

Name Type Description Default
channel_type Union[ChannelTypes, int]

The type of channel to create

required
name str

The name of the channel

required
topic Absent[Optional[str]]

The topic of the channel

MISSING
position Absent[Optional[int]]

The position of the channel in the channel list

MISSING
permission_overwrites Absent[Union[dict, PermissionOverwrite, List[Union[dict, PermissionOverwrite]]]]

Permission overwrites to apply to the channel

MISSING
nsfw bool

Should this channel be marked nsfw

False
bitrate int

The bitrate of this channel, only for voice

64000
user_limit int

The max users that can be in this channel, only for voice

0
rate_limit_per_user int

The time users must wait between sending messages

0
reason Absent[Optional[str]]

The reason for creating this channel

MISSING

Returns:

Type Description
TYPE_GUILD_CHANNEL

The newly created channel.

Source code in naff/models/discord/channel.py
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
async def create_channel(
    self,
    channel_type: Union[ChannelTypes, int],
    name: str,
    topic: Absent[Optional[str]] = MISSING,
    position: Absent[Optional[int]] = MISSING,
    permission_overwrites: Absent[
        Union[dict, PermissionOverwrite, List[Union[dict, PermissionOverwrite]]]
    ] = MISSING,
    nsfw: bool = False,
    bitrate: int = 64000,
    user_limit: int = 0,
    rate_limit_per_user: int = 0,
    reason: Absent[Optional[str]] = MISSING,
) -> "TYPE_GUILD_CHANNEL":
    """
    Create a guild channel within this category, allows for explicit channel type setting.

    Args:
        channel_type: The type of channel to create
        name: The name of the channel
        topic: The topic of the channel
        position: The position of the channel in the channel list
        permission_overwrites: Permission overwrites to apply to the channel
        nsfw: Should this channel be marked nsfw
        bitrate: The bitrate of this channel, only for voice
        user_limit: The max users that can be in this channel, only for voice
        rate_limit_per_user: The time users must wait between sending messages
        reason: The reason for creating this channel

    Returns:
        The newly created channel.

    """
    return await self.guild.create_channel(
        channel_type=channel_type,
        name=name,
        topic=topic,
        position=position,
        permission_overwrites=permission_overwrites,
        category=self.id,
        nsfw=nsfw,
        bitrate=bitrate,
        user_limit=user_limit,
        rate_limit_per_user=rate_limit_per_user,
        reason=reason,
    )

create_text_channel(name, topic=MISSING, position=MISSING, permission_overwrites=MISSING, nsfw=False, rate_limit_per_user=0, reason=MISSING) async

Create a text channel in this guild within this category.

Parameters:

Name Type Description Default
name str

The name of the channel

required
topic Absent[Optional[str]]

The topic of the channel

MISSING
position Absent[Optional[int]]

The position of the channel in the channel list

MISSING
permission_overwrites Absent[Union[dict, PermissionOverwrite, List[Union[dict, PermissionOverwrite]]]]

Permission overwrites to apply to the channel

MISSING
nsfw bool

Should this channel be marked nsfw

False
rate_limit_per_user int

The time users must wait between sending messages

0
reason Absent[Optional[str]]

The reason for creating this channel

MISSING

Returns:

Type Description
GuildText

The newly created text channel.

Source code in naff/models/discord/channel.py
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
async def create_text_channel(
    self,
    name: str,
    topic: Absent[Optional[str]] = MISSING,
    position: Absent[Optional[int]] = MISSING,
    permission_overwrites: Absent[
        Union[dict, PermissionOverwrite, List[Union[dict, PermissionOverwrite]]]
    ] = MISSING,
    nsfw: bool = False,
    rate_limit_per_user: int = 0,
    reason: Absent[Optional[str]] = MISSING,
) -> "GuildText":
    """
    Create a text channel in this guild within this category.

    Args:
        name: The name of the channel
        topic: The topic of the channel
        position: The position of the channel in the channel list
        permission_overwrites: Permission overwrites to apply to the channel
        nsfw: Should this channel be marked nsfw
        rate_limit_per_user: The time users must wait between sending messages
        reason: The reason for creating this channel

    Returns:
       The newly created text channel.

    """
    return await self.create_channel(
        channel_type=ChannelTypes.GUILD_TEXT,
        name=name,
        topic=topic,
        position=position,
        permission_overwrites=permission_overwrites,
        nsfw=nsfw,
        rate_limit_per_user=rate_limit_per_user,
        reason=reason,
    )

create_news_channel(name, topic=MISSING, position=MISSING, permission_overwrites=MISSING, nsfw=False, reason=MISSING) async

Create a news channel in this guild within this category.

Parameters:

Name Type Description Default
name str

The name of the channel

required
topic Absent[Optional[str]]

The topic of the channel

MISSING
position Absent[Optional[int]]

The position of the channel in the channel list

MISSING
permission_overwrites Absent[Union[dict, PermissionOverwrite, List[Union[dict, PermissionOverwrite]]]]

Permission overwrites to apply to the channel

MISSING
nsfw bool

Should this channel be marked nsfw

False
reason Absent[Optional[str]]

The reason for creating this channel

MISSING

Returns:

Type Description
GuildNews

The newly created news channel.

Source code in naff/models/discord/channel.py
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
async def create_news_channel(
    self,
    name: str,
    topic: Absent[Optional[str]] = MISSING,
    position: Absent[Optional[int]] = MISSING,
    permission_overwrites: Absent[
        Union[dict, PermissionOverwrite, List[Union[dict, PermissionOverwrite]]]
    ] = MISSING,
    nsfw: bool = False,
    reason: Absent[Optional[str]] = MISSING,
) -> "GuildNews":
    """
    Create a news channel in this guild within this category.

    Args:
        name: The name of the channel
        topic: The topic of the channel
        position: The position of the channel in the channel list
        permission_overwrites: Permission overwrites to apply to the channel
        nsfw: Should this channel be marked nsfw
        reason: The reason for creating this channel

    Returns:
       The newly created news channel.

    """
    return await self.create_channel(
        channel_type=ChannelTypes.GUILD_NEWS,
        name=name,
        topic=topic,
        position=position,
        permission_overwrites=permission_overwrites,
        nsfw=nsfw,
        reason=reason,
    )

create_voice_channel(name, topic=MISSING, position=MISSING, permission_overwrites=MISSING, nsfw=False, bitrate=64000, user_limit=0, reason=MISSING) async

Create a guild voice channel within this category.

Parameters:

Name Type Description Default
name str

The name of the channel

required
topic Absent[Optional[str]]

The topic of the channel

MISSING
position Absent[Optional[int]]

The position of the channel in the channel list

MISSING
permission_overwrites Absent[Union[dict, PermissionOverwrite, List[Union[dict, PermissionOverwrite]]]]

Permission overwrites to apply to the channel

MISSING
nsfw bool

Should this channel be marked nsfw

False
bitrate int

The bitrate of this channel, only for voice

64000
user_limit int

The max users that can be in this channel, only for voice

0
reason Absent[Optional[str]]

The reason for creating this channel

MISSING

Returns:

Type Description
GuildVoice

The newly created voice channel.

Source code in naff/models/discord/channel.py
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
async def create_voice_channel(
    self,
    name: str,
    topic: Absent[Optional[str]] = MISSING,
    position: Absent[Optional[int]] = MISSING,
    permission_overwrites: Absent[
        Union[dict, PermissionOverwrite, List[Union[dict, PermissionOverwrite]]]
    ] = MISSING,
    nsfw: bool = False,
    bitrate: int = 64000,
    user_limit: int = 0,
    reason: Absent[Optional[str]] = MISSING,
) -> "GuildVoice":
    """
    Create a guild voice channel within this category.

    Args:
        name: The name of the channel
        topic: The topic of the channel
        position: The position of the channel in the channel list
        permission_overwrites: Permission overwrites to apply to the channel
        nsfw: Should this channel be marked nsfw
        bitrate: The bitrate of this channel, only for voice
        user_limit: The max users that can be in this channel, only for voice
        reason: The reason for creating this channel

    Returns:
       The newly created voice channel.

    """
    return await self.create_channel(
        channel_type=ChannelTypes.GUILD_VOICE,
        name=name,
        topic=topic,
        position=position,
        permission_overwrites=permission_overwrites,
        nsfw=nsfw,
        bitrate=bitrate,
        user_limit=user_limit,
        reason=reason,
    )

create_stage_channel(name, topic=MISSING, position=MISSING, permission_overwrites=MISSING, bitrate=64000, user_limit=0, reason=MISSING) async

Create a guild stage channel within this category.

Parameters:

Name Type Description Default
name str

The name of the channel

required
topic Absent[Optional[str]]

The topic of the channel

MISSING
position Absent[Optional[int]]

The position of the channel in the channel list

MISSING
permission_overwrites Absent[Union[dict, PermissionOverwrite, List[Union[dict, PermissionOverwrite]]]]

Permission overwrites to apply to the channel

MISSING
bitrate int

The bitrate of this channel, only for voice

64000
user_limit int

The max users that can be in this channel, only for voice

0
reason Absent[Optional[str]]

The reason for creating this channel

MISSING

Returns:

Type Description
GuildStageVoice

The newly created stage channel.

Source code in naff/models/discord/channel.py
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
async def create_stage_channel(
    self,
    name: str,
    topic: Absent[Optional[str]] = MISSING,
    position: Absent[Optional[int]] = MISSING,
    permission_overwrites: Absent[
        Union[dict, PermissionOverwrite, List[Union[dict, PermissionOverwrite]]]
    ] = MISSING,
    bitrate: int = 64000,
    user_limit: int = 0,
    reason: Absent[Optional[str]] = MISSING,
) -> "GuildStageVoice":
    """
    Create a guild stage channel within this category.

    Args:
        name: The name of the channel
        topic: The topic of the channel
        position: The position of the channel in the channel list
        permission_overwrites: Permission overwrites to apply to the channel
        bitrate: The bitrate of this channel, only for voice
        user_limit: The max users that can be in this channel, only for voice
        reason: The reason for creating this channel

    Returns:
        The newly created stage channel.

    """
    return await self.create_channel(
        channel_type=ChannelTypes.GUILD_STAGE_VOICE,
        name=name,
        topic=topic,
        position=position,
        permission_overwrites=permission_overwrites,
        bitrate=bitrate,
        user_limit=user_limit,
        reason=reason,
    )

GuildNews

Bases: GuildChannel, MessageableMixin, InvitableMixin, ThreadableMixin, WebhookMixin

Source code in naff/models/discord/channel.py
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
@define()
class GuildNews(GuildChannel, MessageableMixin, InvitableMixin, ThreadableMixin, WebhookMixin):
    topic: Optional[str] = field(default=None)
    """The channel topic (0-1024 characters)"""

    async def edit(
        self,
        name: Absent[str] = MISSING,
        position: Absent[int] = MISSING,
        permission_overwrites: Absent[
            Union[dict, PermissionOverwrite, List[Union[dict, PermissionOverwrite]]]
        ] = MISSING,
        parent_id: Absent[Snowflake_Type] = MISSING,
        nsfw: Absent[bool] = MISSING,
        topic: Absent[str] = MISSING,
        channel_type: Absent["ChannelTypes"] = MISSING,
        default_auto_archive_duration: Absent["AutoArchiveDuration"] = MISSING,
        reason: Absent[str] = MISSING,
        **kwargs,
    ) -> Union["GuildNews", "GuildText"]:
        """
        Edit the guild text channel.

        Args:
            name: 1-100 character channel name
            position: the position of the channel in the left-hand listing
            permission_overwrites: a list of PermissionOverwrite
            parent_id:  the parent category `Snowflake_Type` for the channel
            nsfw: whether the channel is nsfw
            topic: 0-1024 character channel topic
            channel_type: the type of channel; only conversion between text and news is supported and only in guilds with the "NEWS" feature
            default_auto_archive_duration: optional AutoArchiveDuration
            reason: An optional reason for the audit log

        Returns:
            The edited channel.

        """
        return await super().edit(
            name=name,
            position=position,
            permission_overwrites=permission_overwrites,
            parent_id=parent_id,
            nsfw=nsfw,
            topic=topic,
            type=channel_type,
            default_auto_archive_duration=default_auto_archive_duration,
            reason=reason,
            **kwargs,
        )

    async def follow(self, webhook_channel_id: Snowflake_Type) -> None:
        """
        Follow this channel.

        Args:
            webhook_channel_id: The ID of the channel to post messages from this channel to

        """
        await self._client.http.follow_news_channel(self.id, webhook_channel_id)

    async def create_thread_from_message(
        self,
        name: str,
        message: Snowflake_Type,
        auto_archive_duration: AutoArchiveDuration = AutoArchiveDuration.ONE_DAY,
        reason: Absent[str] = None,
    ) -> "GuildNewsThread":
        """
        Creates a new news thread in this channel.

        Args:
            name: 1-100 character thread name.
            message: The message to connect this thread to.
            auto_archive_duration: Time before the thread will be automatically archived. Note 3 day and 7 day archive durations require the server to be boosted.
            reason: The reason for creating this thread.

        Returns:
            The created public thread, if successful

        """
        return await self.create_thread(
            name=name,
            message=message,
            auto_archive_duration=auto_archive_duration,
            reason=reason,
        )

topic: Optional[str] = field(default=None) class-attribute

The channel topic (0-1024 characters)

edit(name=MISSING, position=MISSING, permission_overwrites=MISSING, parent_id=MISSING, nsfw=MISSING, topic=MISSING, channel_type=MISSING, default_auto_archive_duration=MISSING, reason=MISSING, **kwargs) async

Edit the guild text channel.

Parameters:

Name Type Description Default
name Absent[str]

1-100 character channel name

MISSING
position Absent[int]

the position of the channel in the left-hand listing

MISSING
permission_overwrites Absent[Union[dict, PermissionOverwrite, List[Union[dict, PermissionOverwrite]]]]

a list of PermissionOverwrite

MISSING
parent_id Absent[Snowflake_Type]

the parent category Snowflake_Type for the channel

MISSING
nsfw Absent[bool]

whether the channel is nsfw

MISSING
topic Absent[str]

0-1024 character channel topic

MISSING
channel_type Absent[ChannelTypes]

the type of channel; only conversion between text and news is supported and only in guilds with the "NEWS" feature

MISSING
default_auto_archive_duration Absent[AutoArchiveDuration]

optional AutoArchiveDuration

MISSING
reason Absent[str]

An optional reason for the audit log

MISSING

Returns:

Type Description
Union[GuildNews, GuildText]

The edited channel.

Source code in naff/models/discord/channel.py
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
async def edit(
    self,
    name: Absent[str] = MISSING,
    position: Absent[int] = MISSING,
    permission_overwrites: Absent[
        Union[dict, PermissionOverwrite, List[Union[dict, PermissionOverwrite]]]
    ] = MISSING,
    parent_id: Absent[Snowflake_Type] = MISSING,
    nsfw: Absent[bool] = MISSING,
    topic: Absent[str] = MISSING,
    channel_type: Absent["ChannelTypes"] = MISSING,
    default_auto_archive_duration: Absent["AutoArchiveDuration"] = MISSING,
    reason: Absent[str] = MISSING,
    **kwargs,
) -> Union["GuildNews", "GuildText"]:
    """
    Edit the guild text channel.

    Args:
        name: 1-100 character channel name
        position: the position of the channel in the left-hand listing
        permission_overwrites: a list of PermissionOverwrite
        parent_id:  the parent category `Snowflake_Type` for the channel
        nsfw: whether the channel is nsfw
        topic: 0-1024 character channel topic
        channel_type: the type of channel; only conversion between text and news is supported and only in guilds with the "NEWS" feature
        default_auto_archive_duration: optional AutoArchiveDuration
        reason: An optional reason for the audit log

    Returns:
        The edited channel.

    """
    return await super().edit(
        name=name,
        position=position,
        permission_overwrites=permission_overwrites,
        parent_id=parent_id,
        nsfw=nsfw,
        topic=topic,
        type=channel_type,
        default_auto_archive_duration=default_auto_archive_duration,
        reason=reason,
        **kwargs,
    )

follow(webhook_channel_id) async

Follow this channel.

Parameters:

Name Type Description Default
webhook_channel_id Snowflake_Type

The ID of the channel to post messages from this channel to

required
Source code in naff/models/discord/channel.py
1576
1577
1578
1579
1580
1581
1582
1583
1584
async def follow(self, webhook_channel_id: Snowflake_Type) -> None:
    """
    Follow this channel.

    Args:
        webhook_channel_id: The ID of the channel to post messages from this channel to

    """
    await self._client.http.follow_news_channel(self.id, webhook_channel_id)

create_thread_from_message(name, message, auto_archive_duration=AutoArchiveDuration.ONE_DAY, reason=None) async

Creates a new news thread in this channel.

Parameters:

Name Type Description Default
name str

1-100 character thread name.

required
message Snowflake_Type

The message to connect this thread to.

required
auto_archive_duration AutoArchiveDuration

Time before the thread will be automatically archived. Note 3 day and 7 day archive durations require the server to be boosted.

AutoArchiveDuration.ONE_DAY
reason Absent[str]

The reason for creating this thread.

None

Returns:

Type Description
GuildNewsThread

The created public thread, if successful

Source code in naff/models/discord/channel.py
1586
1587
1588
1589
1590
1591
1592
1593
1594
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
async def create_thread_from_message(
    self,
    name: str,
    message: Snowflake_Type,
    auto_archive_duration: AutoArchiveDuration = AutoArchiveDuration.ONE_DAY,
    reason: Absent[str] = None,
) -> "GuildNewsThread":
    """
    Creates a new news thread in this channel.

    Args:
        name: 1-100 character thread name.
        message: The message to connect this thread to.
        auto_archive_duration: Time before the thread will be automatically archived. Note 3 day and 7 day archive durations require the server to be boosted.
        reason: The reason for creating this thread.

    Returns:
        The created public thread, if successful

    """
    return await self.create_thread(
        name=name,
        message=message,
        auto_archive_duration=auto_archive_duration,
        reason=reason,
    )

GuildText

Bases: GuildChannel, MessageableMixin, InvitableMixin, ThreadableMixin, WebhookMixin

Source code in naff/models/discord/channel.py
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
1631
1632
1633
1634
1635
1636
1637
1638
1639
1640
1641
1642
1643
1644
1645
1646
1647
1648
1649
1650
1651
1652
1653
1654
1655
1656
1657
1658
1659
1660
1661
1662
1663
1664
1665
1666
1667
1668
1669
1670
1671
1672
1673
1674
1675
1676
1677
1678
1679
1680
1681
1682
1683
1684
1685
1686
1687
1688
1689
1690
1691
1692
1693
1694
1695
1696
1697
1698
1699
1700
1701
1702
1703
1704
1705
1706
1707
1708
1709
1710
1711
1712
1713
1714
1715
1716
1717
1718
1719
1720
1721
1722
1723
1724
1725
1726
1727
1728
1729
1730
1731
1732
1733
1734
1735
1736
1737
1738
1739
1740
1741
1742
1743
1744
1745
1746
1747
@define()
class GuildText(GuildChannel, MessageableMixin, InvitableMixin, ThreadableMixin, WebhookMixin):
    topic: Optional[str] = field(default=None)
    """The channel topic (0-1024 characters)"""
    rate_limit_per_user: int = field(default=0)
    """Amount of seconds a user has to wait before sending another message (0-21600)"""

    async def edit(
        self,
        name: Absent[str] = MISSING,
        position: Absent[int] = MISSING,
        permission_overwrites: Absent[
            Union[dict, PermissionOverwrite, List[Union[dict, PermissionOverwrite]]]
        ] = MISSING,
        parent_id: Absent[Snowflake_Type] = MISSING,
        nsfw: Absent[bool] = MISSING,
        topic: Absent[str] = MISSING,
        channel_type: Absent["ChannelTypes"] = MISSING,
        default_auto_archive_duration: Absent["AutoArchiveDuration"] = MISSING,
        rate_limit_per_user: Absent[int] = MISSING,
        reason: Absent[str] = MISSING,
        **kwargs,
    ) -> Union["GuildText", "GuildNews"]:
        """
        Edit the guild text channel.

        Args:
            name: 1-100 character channel name
            position: the position of the channel in the left-hand listing
            permission_overwrites: a list of PermissionOverwrite
            parent_id:  the parent category `Snowflake_Type` for the channel
            nsfw: whether the channel is nsfw
            topic: 0-1024 character channel topic
            channel_type: the type of channel; only conversion between text and news is supported and only in guilds with the "NEWS" feature
            default_auto_archive_duration: optional AutoArchiveDuration
            rate_limit_per_user: amount of seconds a user has to wait before sending another message (0-21600)
            reason: An optional reason for the audit log

        Returns:
            The edited channel.

        """
        return await super().edit(
            name=name,
            position=position,
            permission_overwrites=permission_overwrites,
            parent_id=parent_id,
            nsfw=nsfw,
            topic=topic,
            type=channel_type,
            default_auto_archive_duration=default_auto_archive_duration,
            reason=reason,
            **kwargs,
        )

    async def create_public_thread(
        self,
        name: str,
        auto_archive_duration: AutoArchiveDuration = AutoArchiveDuration.ONE_DAY,
        reason: Absent[str] = None,
    ) -> "GuildPublicThread":
        """
        Creates a new public thread in this channel.

        Args:
            name: 1-100 character thread name.
            auto_archive_duration: Time before the thread will be automatically archived. Note 3 day and 7 day archive durations require the server to be boosted.
            reason: The reason for creating this thread.

        Returns:
            The created public thread, if successful

        """
        return await self.create_thread(
            name=name,
            thread_type=ChannelTypes.GUILD_PUBLIC_THREAD,
            auto_archive_duration=auto_archive_duration,
            reason=reason,
        )

    async def create_private_thread(
        self,
        name: str,
        invitable: Absent[bool] = MISSING,
        auto_archive_duration: AutoArchiveDuration = AutoArchiveDuration.ONE_DAY,
        reason: Absent[str] = None,
    ) -> "GuildPrivateThread":
        """
        Creates a new private thread in this channel.

        Args:
            name: 1-100 character thread name.
            invitable: whether non-moderators can add other non-moderators to a thread.
            auto_archive_duration: Time before the thread will be automatically archived. Note 3 day and 7 day archive durations require the server to be boosted.
            reason: The reason for creating this thread.

        Returns:
            The created thread, if successful

        """
        return await self.create_thread(
            name=name,
            thread_type=ChannelTypes.GUILD_PRIVATE_THREAD,
            invitable=invitable,
            auto_archive_duration=auto_archive_duration,
            reason=reason,
        )

    async def create_thread_from_message(
        self,
        name: str,
        message: Snowflake_Type,
        auto_archive_duration: AutoArchiveDuration = AutoArchiveDuration.ONE_DAY,
        reason: Absent[str] = None,
    ) -> "GuildPublicThread":
        """
        Creates a new public thread in this channel.

        Args:
            name: 1-100 character thread name.
            message: The message to connect this thread to.
            auto_archive_duration: Time before the thread will be automatically archived. Note 3 day and 7 day archive durations require the server to be boosted.
            reason: The reason for creating this thread.

        Returns:
            The created public thread, if successful

        """
        return await self.create_thread(
            name=name,
            message=message,
            auto_archive_duration=auto_archive_duration,
            reason=reason,
        )

topic: Optional[str] = field(default=None) class-attribute

The channel topic (0-1024 characters)

rate_limit_per_user: int = field(default=0) class-attribute

Amount of seconds a user has to wait before sending another message (0-21600)

edit(name=MISSING, position=MISSING, permission_overwrites=MISSING, parent_id=MISSING, nsfw=MISSING, topic=MISSING, channel_type=MISSING, default_auto_archive_duration=MISSING, rate_limit_per_user=MISSING, reason=MISSING, **kwargs) async

Edit the guild text channel.

Parameters:

Name Type Description Default
name Absent[str]

1-100 character channel name

MISSING
position Absent[int]

the position of the channel in the left-hand listing

MISSING
permission_overwrites Absent[Union[dict, PermissionOverwrite, List[Union[dict, PermissionOverwrite]]]]

a list of PermissionOverwrite

MISSING
parent_id Absent[Snowflake_Type]

the parent category Snowflake_Type for the channel

MISSING
nsfw Absent[bool]

whether the channel is nsfw

MISSING
topic Absent[str]

0-1024 character channel topic

MISSING
channel_type Absent[ChannelTypes]

the type of channel; only conversion between text and news is supported and only in guilds with the "NEWS" feature

MISSING
default_auto_archive_duration Absent[AutoArchiveDuration]

optional AutoArchiveDuration

MISSING
rate_limit_per_user Absent[int]

amount of seconds a user has to wait before sending another message (0-21600)

MISSING
reason Absent[str]

An optional reason for the audit log

MISSING

Returns:

Type Description
Union[GuildText, GuildNews]

The edited channel.

Source code in naff/models/discord/channel.py
1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
1631
1632
1633
1634
1635
1636
1637
1638
1639
1640
1641
1642
1643
1644
1645
1646
1647
1648
1649
1650
1651
1652
1653
1654
1655
1656
1657
1658
1659
1660
1661
1662
1663
1664
1665
1666
1667
async def edit(
    self,
    name: Absent[str] = MISSING,
    position: Absent[int] = MISSING,
    permission_overwrites: Absent[
        Union[dict, PermissionOverwrite, List[Union[dict, PermissionOverwrite]]]
    ] = MISSING,
    parent_id: Absent[Snowflake_Type] = MISSING,
    nsfw: Absent[bool] = MISSING,
    topic: Absent[str] = MISSING,
    channel_type: Absent["ChannelTypes"] = MISSING,
    default_auto_archive_duration: Absent["AutoArchiveDuration"] = MISSING,
    rate_limit_per_user: Absent[int] = MISSING,
    reason: Absent[str] = MISSING,
    **kwargs,
) -> Union["GuildText", "GuildNews"]:
    """
    Edit the guild text channel.

    Args:
        name: 1-100 character channel name
        position: the position of the channel in the left-hand listing
        permission_overwrites: a list of PermissionOverwrite
        parent_id:  the parent category `Snowflake_Type` for the channel
        nsfw: whether the channel is nsfw
        topic: 0-1024 character channel topic
        channel_type: the type of channel; only conversion between text and news is supported and only in guilds with the "NEWS" feature
        default_auto_archive_duration: optional AutoArchiveDuration
        rate_limit_per_user: amount of seconds a user has to wait before sending another message (0-21600)
        reason: An optional reason for the audit log

    Returns:
        The edited channel.

    """
    return await super().edit(
        name=name,
        position=position,
        permission_overwrites=permission_overwrites,
        parent_id=parent_id,
        nsfw=nsfw,
        topic=topic,
        type=channel_type,
        default_auto_archive_duration=default_auto_archive_duration,
        reason=reason,
        **kwargs,
    )

create_public_thread(name, auto_archive_duration=AutoArchiveDuration.ONE_DAY, reason=None) async

Creates a new public thread in this channel.

Parameters:

Name Type Description Default
name str

1-100 character thread name.

required
auto_archive_duration AutoArchiveDuration

Time before the thread will be automatically archived. Note 3 day and 7 day archive durations require the server to be boosted.

AutoArchiveDuration.ONE_DAY
reason Absent[str]

The reason for creating this thread.

None

Returns:

Type Description
GuildPublicThread

The created public thread, if successful

Source code in naff/models/discord/channel.py
1669
1670
1671
1672
1673
1674
1675
1676
1677
1678
1679
1680
1681
1682
1683
1684
1685
1686
1687
1688
1689
1690
1691
1692
async def create_public_thread(
    self,
    name: str,
    auto_archive_duration: AutoArchiveDuration = AutoArchiveDuration.ONE_DAY,
    reason: Absent[str] = None,
) -> "GuildPublicThread":
    """
    Creates a new public thread in this channel.

    Args:
        name: 1-100 character thread name.
        auto_archive_duration: Time before the thread will be automatically archived. Note 3 day and 7 day archive durations require the server to be boosted.
        reason: The reason for creating this thread.

    Returns:
        The created public thread, if successful

    """
    return await self.create_thread(
        name=name,
        thread_type=ChannelTypes.GUILD_PUBLIC_THREAD,
        auto_archive_duration=auto_archive_duration,
        reason=reason,
    )

create_private_thread(name, invitable=MISSING, auto_archive_duration=AutoArchiveDuration.ONE_DAY, reason=None) async

Creates a new private thread in this channel.

Parameters:

Name Type Description Default
name str

1-100 character thread name.

required
invitable Absent[bool]

whether non-moderators can add other non-moderators to a thread.

MISSING
auto_archive_duration AutoArchiveDuration

Time before the thread will be automatically archived. Note 3 day and 7 day archive durations require the server to be boosted.

AutoArchiveDuration.ONE_DAY
reason Absent[str]

The reason for creating this thread.

None

Returns:

Type Description
GuildPrivateThread

The created thread, if successful

Source code in naff/models/discord/channel.py
1694
1695
1696
1697
1698
1699
1700
1701
1702
1703
1704
1705
1706
1707
1708
1709
1710
1711
1712
1713
1714
1715
1716
1717
1718
1719
1720
async def create_private_thread(
    self,
    name: str,
    invitable: Absent[bool] = MISSING,
    auto_archive_duration: AutoArchiveDuration = AutoArchiveDuration.ONE_DAY,
    reason: Absent[str] = None,
) -> "GuildPrivateThread":
    """
    Creates a new private thread in this channel.

    Args:
        name: 1-100 character thread name.
        invitable: whether non-moderators can add other non-moderators to a thread.
        auto_archive_duration: Time before the thread will be automatically archived. Note 3 day and 7 day archive durations require the server to be boosted.
        reason: The reason for creating this thread.

    Returns:
        The created thread, if successful

    """
    return await self.create_thread(
        name=name,
        thread_type=ChannelTypes.GUILD_PRIVATE_THREAD,
        invitable=invitable,
        auto_archive_duration=auto_archive_duration,
        reason=reason,
    )

create_thread_from_message(name, message, auto_archive_duration=AutoArchiveDuration.ONE_DAY, reason=None) async

Creates a new public thread in this channel.

Parameters:

Name Type Description Default
name str

1-100 character thread name.

required
message Snowflake_Type

The message to connect this thread to.

required
auto_archive_duration AutoArchiveDuration

Time before the thread will be automatically archived. Note 3 day and 7 day archive durations require the server to be boosted.

AutoArchiveDuration.ONE_DAY
reason Absent[str]

The reason for creating this thread.

None

Returns:

Type Description
GuildPublicThread

The created public thread, if successful

Source code in naff/models/discord/channel.py
1722
1723
1724
1725
1726
1727
1728
1729
1730
1731
1732
1733
1734
1735
1736
1737
1738
1739
1740
1741
1742
1743
1744
1745
1746
1747
async def create_thread_from_message(
    self,
    name: str,
    message: Snowflake_Type,
    auto_archive_duration: AutoArchiveDuration = AutoArchiveDuration.ONE_DAY,
    reason: Absent[str] = None,
) -> "GuildPublicThread":
    """
    Creates a new public thread in this channel.

    Args:
        name: 1-100 character thread name.
        message: The message to connect this thread to.
        auto_archive_duration: Time before the thread will be automatically archived. Note 3 day and 7 day archive durations require the server to be boosted.
        reason: The reason for creating this thread.

    Returns:
        The created public thread, if successful

    """
    return await self.create_thread(
        name=name,
        message=message,
        auto_archive_duration=auto_archive_duration,
        reason=reason,
    )

ThreadChannel

Bases: BaseChannel, MessageableMixin, WebhookMixin

Source code in naff/models/discord/channel.py
1754
1755
1756
1757
1758
1759
1760
1761
1762
1763
1764
1765
1766
1767
1768
1769
1770
1771
1772
1773
1774
1775
1776
1777
1778
1779
1780
1781
1782
1783
1784
1785
1786
1787
1788
1789
1790
1791
1792
1793
1794
1795
1796
1797
1798
1799
1800
1801
1802
1803
1804
1805
1806
1807
1808
1809
1810
1811
1812
1813
1814
1815
1816
1817
1818
1819
1820
1821
1822
1823
1824
1825
1826
1827
1828
1829
1830
1831
1832
1833
1834
1835
1836
1837
1838
1839
1840
1841
1842
1843
1844
1845
1846
1847
1848
1849
1850
1851
1852
1853
1854
1855
1856
1857
1858
1859
1860
1861
1862
1863
1864
1865
@define(slots=False)
class ThreadChannel(BaseChannel, MessageableMixin, WebhookMixin):
    parent_id: Snowflake_Type = field(default=None, converter=optional_c(to_snowflake))
    """id of the text channel this thread was created"""
    owner_id: Snowflake_Type = field(default=None, converter=optional_c(to_snowflake))
    """id of the creator of the thread"""
    topic: Optional[str] = field(default=None)
    """The thread topic (0-1024 characters)"""
    message_count: int = field(default=0)
    """An approximate count of messages in a thread, stops counting at 50"""
    member_count: int = field(default=0)
    """An approximate count of users in a thread, stops counting at 50"""
    archived: bool = field(default=False)
    """Whether the thread is archived"""
    auto_archive_duration: int = field(
        default=attrs.Factory(lambda self: self.default_auto_archive_duration, takes_self=True)
    )
    """Duration in minutes to automatically archive the thread after recent activity, can be set to: 60, 1440, 4320, 10080"""
    locked: bool = field(default=False)
    """Whether the thread is locked"""
    archive_timestamp: Optional["models.Timestamp"] = field(default=None, converter=optional_c(timestamp_converter))
    """Timestamp when the thread's archive status was last changed, used for calculating recent activity"""
    create_timestamp: Optional["models.Timestamp"] = field(default=None, converter=optional_c(timestamp_converter))
    """Timestamp when the thread was created"""
    flags: ChannelFlags = field(default=ChannelFlags.NONE, converter=ChannelFlags)
    """Flags for the thread"""

    _guild_id: Snowflake_Type = field(default=None, converter=optional_c(to_snowflake))

    @classmethod
    def _process_dict(cls, data: Dict[str, Any], client: "Client") -> Dict[str, Any]:
        data = super()._process_dict(data, client)
        thread_metadata: dict = data.get("thread_metadata", {})
        data.update(thread_metadata)
        return data

    @property
    def is_private(self) -> bool:
        """Is this a private thread?"""
        return self.type == ChannelTypes.GUILD_PRIVATE_THREAD

    @property
    def guild(self) -> "models.Guild":
        """The guild this channel belongs to."""
        return self._client.cache.get_guild(self._guild_id)

    @property
    def parent_channel(self) -> Union[GuildText, "GuildForum"]:
        """The channel this thread is a child of."""
        return self._client.cache.get_channel(self.parent_id)

    @property
    def parent_message(self) -> Optional["Message"]:
        """The message this thread is a child of."""
        return self._client.cache.get_message(self.parent_id, self.id)

    @property
    def mention(self) -> str:
        """Returns a string that would mention this thread."""
        return f"<#{self.id}>"

    @property
    def permission_overwrites(self) -> List["PermissionOverwrite"]:
        """The permission overwrites for this channel."""
        return []

    async def fetch_members(self) -> List["models.ThreadMember"]:
        """Get the members that have access to this thread."""
        members_data = await self._client.http.list_thread_members(self.id)
        return models.ThreadMember.from_list(members_data, self._client)

    async def add_member(self, member: Union["models.Member", Snowflake_Type]) -> None:
        """
        Add a member to this thread.

        Args:
            member: The member to add

        """
        await self._client.http.add_thread_member(self.id, to_snowflake(member))

    async def remove_member(self, member: Union["models.Member", Snowflake_Type]) -> None:
        """
        Remove a member from this thread.

        Args:
            member: The member to remove

        """
        await self._client.http.remove_thread_member(self.id, to_snowflake(member))

    async def join(self) -> None:
        """Join this thread."""
        await self._client.http.join_thread(self.id)

    async def leave(self) -> None:
        """Leave this thread."""
        await self._client.http.leave_thread(self.id)

    async def archive(self, locked: bool = False, reason: Absent[str] = MISSING) -> "TYPE_THREAD_CHANNEL":
        """
        Helper method to archive this thread.

        Args:
            locked: whether the thread is locked; when a thread is locked, only users with MANAGE_THREADS can unarchive it
            reason: The reason for this archive

        Returns:
            The archived thread channel object.

        """
        return await super().edit(locked=locked, archived=True, reason=reason)

parent_id: Snowflake_Type = field(default=None, converter=optional_c(to_snowflake)) class-attribute

id of the text channel this thread was created

owner_id: Snowflake_Type = field(default=None, converter=optional_c(to_snowflake)) class-attribute

id of the creator of the thread

topic: Optional[str] = field(default=None) class-attribute

The thread topic (0-1024 characters)

message_count: int = field(default=0) class-attribute

An approximate count of messages in a thread, stops counting at 50

member_count: int = field(default=0) class-attribute

An approximate count of users in a thread, stops counting at 50

archived: bool = field(default=False) class-attribute

Whether the thread is archived

auto_archive_duration: int = field(default=attrs.Factory(lambda self: self.default_auto_archive_duration, takes_self=True)) class-attribute

Duration in minutes to automatically archive the thread after recent activity, can be set to: 60, 1440, 4320, 10080

locked: bool = field(default=False) class-attribute

Whether the thread is locked

archive_timestamp: Optional[models.Timestamp] = field(default=None, converter=optional_c(timestamp_converter)) class-attribute

Timestamp when the thread's archive status was last changed, used for calculating recent activity

create_timestamp: Optional[models.Timestamp] = field(default=None, converter=optional_c(timestamp_converter)) class-attribute

Timestamp when the thread was created

flags: ChannelFlags = field(default=ChannelFlags.NONE, converter=ChannelFlags) class-attribute

Flags for the thread

is_private() property

Is this a private thread?

Source code in naff/models/discord/channel.py
1790
1791
1792
1793
@property
def is_private(self) -> bool:
    """Is this a private thread?"""
    return self.type == ChannelTypes.GUILD_PRIVATE_THREAD

guild() property

The guild this channel belongs to.

Source code in naff/models/discord/channel.py
1795
1796
1797
1798
@property
def guild(self) -> "models.Guild":
    """The guild this channel belongs to."""
    return self._client.cache.get_guild(self._guild_id)

parent_channel() property

The channel this thread is a child of.

Source code in naff/models/discord/channel.py
1800
1801
1802
1803
@property
def parent_channel(self) -> Union[GuildText, "GuildForum"]:
    """The channel this thread is a child of."""
    return self._client.cache.get_channel(self.parent_id)

parent_message() property

The message this thread is a child of.

Source code in naff/models/discord/channel.py
1805
1806
1807
1808
@property
def parent_message(self) -> Optional["Message"]:
    """The message this thread is a child of."""
    return self._client.cache.get_message(self.parent_id, self.id)

mention() property

Returns a string that would mention this thread.

Source code in naff/models/discord/channel.py
1810
1811
1812
1813
@property
def mention(self) -> str:
    """Returns a string that would mention this thread."""
    return f"<#{self.id}>"

permission_overwrites() property

The permission overwrites for this channel.

Source code in naff/models/discord/channel.py
1815
1816
1817
1818
@property
def permission_overwrites(self) -> List["PermissionOverwrite"]:
    """The permission overwrites for this channel."""
    return []

fetch_members() async

Get the members that have access to this thread.

Source code in naff/models/discord/channel.py
1820
1821
1822
1823
async def fetch_members(self) -> List["models.ThreadMember"]:
    """Get the members that have access to this thread."""
    members_data = await self._client.http.list_thread_members(self.id)
    return models.ThreadMember.from_list(members_data, self._client)

add_member(member) async

Add a member to this thread.

Parameters:

Name Type Description Default
member Union[Member, Snowflake_Type]

The member to add

required
Source code in naff/models/discord/channel.py
1825
1826
1827
1828
1829
1830
1831
1832
1833
async def add_member(self, member: Union["models.Member", Snowflake_Type]) -> None:
    """
    Add a member to this thread.

    Args:
        member: The member to add

    """
    await self._client.http.add_thread_member(self.id, to_snowflake(member))

remove_member(member) async

Remove a member from this thread.

Parameters:

Name Type Description Default
member Union[Member, Snowflake_Type]

The member to remove

required
Source code in naff/models/discord/channel.py
1835
1836
1837
1838
1839
1840
1841
1842
1843
async def remove_member(self, member: Union["models.Member", Snowflake_Type]) -> None:
    """
    Remove a member from this thread.

    Args:
        member: The member to remove

    """
    await self._client.http.remove_thread_member(self.id, to_snowflake(member))

join() async

Join this thread.

Source code in naff/models/discord/channel.py
1845
1846
1847
async def join(self) -> None:
    """Join this thread."""
    await self._client.http.join_thread(self.id)

leave() async

Leave this thread.

Source code in naff/models/discord/channel.py
1849
1850
1851
async def leave(self) -> None:
    """Leave this thread."""
    await self._client.http.leave_thread(self.id)

archive(locked=False, reason=MISSING) async

Helper method to archive this thread.

Parameters:

Name Type Description Default
locked bool

whether the thread is locked; when a thread is locked, only users with MANAGE_THREADS can unarchive it

False
reason Absent[str]

The reason for this archive

MISSING

Returns:

Type Description
TYPE_THREAD_CHANNEL

The archived thread channel object.

Source code in naff/models/discord/channel.py
1853
1854
1855
1856
1857
1858
1859
1860
1861
1862
1863
1864
1865
async def archive(self, locked: bool = False, reason: Absent[str] = MISSING) -> "TYPE_THREAD_CHANNEL":
    """
    Helper method to archive this thread.

    Args:
        locked: whether the thread is locked; when a thread is locked, only users with MANAGE_THREADS can unarchive it
        reason: The reason for this archive

    Returns:
        The archived thread channel object.

    """
    return await super().edit(locked=locked, archived=True, reason=reason)

GuildNewsThread

Bases: ThreadChannel

Source code in naff/models/discord/channel.py
1868
1869
1870
1871
1872
1873
1874
1875
1876
1877
1878
1879
1880
1881
1882
1883
1884
1885
1886
1887
1888
1889
1890
1891
1892
1893
1894
1895
1896
1897
1898
1899
1900
1901
1902
1903
@define()
class GuildNewsThread(ThreadChannel):
    async def edit(
        self,
        name: Absent[str] = MISSING,
        archived: Absent[bool] = MISSING,
        auto_archive_duration: Absent[AutoArchiveDuration] = MISSING,
        locked: Absent[bool] = MISSING,
        rate_limit_per_user: Absent[int] = MISSING,
        reason: Absent[str] = MISSING,
        **kwargs,
    ) -> "GuildNewsThread":
        """
        Edit this thread.

        Args:
            name: 1-100 character channel name
            archived: whether the thread is archived
            auto_archive_duration: duration in minutes to automatically archive the thread after recent activity, can be set to: 60, 1440, 4320, 10080
            locked: whether the thread is locked; when a thread is locked, only users with MANAGE_THREADS can unarchive it
            rate_limit_per_user: amount of seconds a user has to wait before sending another message (0-21600)
            reason: The reason for this change

        Returns:
            The edited thread channel object.

        """
        return await super().edit(
            name=name,
            archived=archived,
            auto_archive_duration=auto_archive_duration,
            locked=locked,
            rate_limit_per_user=rate_limit_per_user,
            reason=reason,
            **kwargs,
        )

edit(name=MISSING, archived=MISSING, auto_archive_duration=MISSING, locked=MISSING, rate_limit_per_user=MISSING, reason=MISSING, **kwargs) async

Edit this thread.

Parameters:

Name Type Description Default
name Absent[str]

1-100 character channel name

MISSING
archived Absent[bool]

whether the thread is archived

MISSING
auto_archive_duration Absent[AutoArchiveDuration]

duration in minutes to automatically archive the thread after recent activity, can be set to: 60, 1440, 4320, 10080

MISSING
locked Absent[bool]

whether the thread is locked; when a thread is locked, only users with MANAGE_THREADS can unarchive it

MISSING
rate_limit_per_user Absent[int]

amount of seconds a user has to wait before sending another message (0-21600)

MISSING
reason Absent[str]

The reason for this change

MISSING

Returns:

Type Description
GuildNewsThread

The edited thread channel object.

Source code in naff/models/discord/channel.py
1870
1871
1872
1873
1874
1875
1876
1877
1878
1879
1880
1881
1882
1883
1884
1885
1886
1887
1888
1889
1890
1891
1892
1893
1894
1895
1896
1897
1898
1899
1900
1901
1902
1903
async def edit(
    self,
    name: Absent[str] = MISSING,
    archived: Absent[bool] = MISSING,
    auto_archive_duration: Absent[AutoArchiveDuration] = MISSING,
    locked: Absent[bool] = MISSING,
    rate_limit_per_user: Absent[int] = MISSING,
    reason: Absent[str] = MISSING,
    **kwargs,
) -> "GuildNewsThread":
    """
    Edit this thread.

    Args:
        name: 1-100 character channel name
        archived: whether the thread is archived
        auto_archive_duration: duration in minutes to automatically archive the thread after recent activity, can be set to: 60, 1440, 4320, 10080
        locked: whether the thread is locked; when a thread is locked, only users with MANAGE_THREADS can unarchive it
        rate_limit_per_user: amount of seconds a user has to wait before sending another message (0-21600)
        reason: The reason for this change

    Returns:
        The edited thread channel object.

    """
    return await super().edit(
        name=name,
        archived=archived,
        auto_archive_duration=auto_archive_duration,
        locked=locked,
        rate_limit_per_user=rate_limit_per_user,
        reason=reason,
        **kwargs,
    )

GuildPublicThread

Bases: ThreadChannel

Source code in naff/models/discord/channel.py
1906
1907
1908
1909
1910
1911
1912
1913
1914
1915
1916
1917
1918
1919
1920
1921
1922
1923
1924
1925
1926
1927
1928
1929
1930
1931
1932
1933
1934
1935
1936
1937
1938
1939
1940
1941
1942
1943
1944
1945
1946
1947
1948
1949
1950
1951
1952
1953
1954
1955
1956
1957
1958
1959
1960
1961
1962
1963
1964
1965
1966
1967
1968
1969
1970
1971
1972
1973
1974
1975
1976
1977
1978
@define()
class GuildPublicThread(ThreadChannel):

    _applied_tags: List[Snowflake_Type] = field(factory=list)

    async def edit(
        self,
        name: Absent[str] = MISSING,
        archived: Absent[bool] = MISSING,
        auto_archive_duration: Absent[AutoArchiveDuration] = MISSING,
        applied_tags: Absent[List[Union[Snowflake_Type, ThreadTag]]] = MISSING,
        locked: Absent[bool] = MISSING,
        rate_limit_per_user: Absent[int] = MISSING,
        flags: Absent[Union[int, ChannelFlags]] = MISSING,
        reason: Absent[str] = MISSING,
        **kwargs,
    ) -> "GuildPublicThread":
        """
        Edit this thread.

        Args:
            name: 1-100 character channel name
            archived: whether the thread is archived
            applied_tags: list of tags to apply to a forum post (!!! This is for forum threads only)
            auto_archive_duration: duration in minutes to automatically archive the thread after recent activity, can be set to: 60, 1440, 4320, 10080
            locked: whether the thread is locked; when a thread is locked, only users with MANAGE_THREADS can unarchive it
            rate_limit_per_user: amount of seconds a user has to wait before sending another message (0-21600)
            flags: channel flags for forum threads
            reason: The reason for this change

        Returns:
            The edited thread channel object.
        """
        if applied_tags != MISSING:
            applied_tags = [str(tag.id) if isinstance(tag, ThreadTag) else str(tag) for tag in applied_tags]

        return await super().edit(
            name=name,
            archived=archived,
            auto_archive_duration=auto_archive_duration,
            applied_tags=applied_tags,
            locked=locked,
            rate_limit_per_user=rate_limit_per_user,
            reason=reason,
            flags=flags,
            **kwargs,
        )

    @property
    def applied_tags(self) -> list[ThreadTag]:
        """
        The tags applied to this thread.

        !!! note
            This is only on forum threads.

        """
        if not isinstance(self.parent_channel, GuildForum):
            raise AttributeError("This is only available on forum threads.")
        return [tag for tag in self.parent_channel.available_tags if str(tag.id) in self._applied_tags]

    @property
    def initial_post(self) -> Optional["Message"]:
        """
        The initial message posted by the OP.

        !!! note
            This is only on forum threads.

        """
        if not isinstance(self.parent_channel, GuildForum):
            raise AttributeError("This is only available on forum threads.")
        return self.get_message(self.id)

edit(name=MISSING, archived=MISSING, auto_archive_duration=MISSING, applied_tags=MISSING, locked=MISSING, rate_limit_per_user=MISSING, flags=MISSING, reason=MISSING, **kwargs) async

Edit this thread.

Parameters:

Name Type Description Default
name Absent[str]

1-100 character channel name

MISSING
archived Absent[bool]

whether the thread is archived

MISSING
applied_tags Absent[List[Union[Snowflake_Type, ThreadTag]]]

list of tags to apply to a forum post (!!! This is for forum threads only)

MISSING
auto_archive_duration Absent[AutoArchiveDuration]

duration in minutes to automatically archive the thread after recent activity, can be set to: 60, 1440, 4320, 10080

MISSING
locked Absent[bool]

whether the thread is locked; when a thread is locked, only users with MANAGE_THREADS can unarchive it

MISSING
rate_limit_per_user Absent[int]

amount of seconds a user has to wait before sending another message (0-21600)

MISSING
flags Absent[Union[int, ChannelFlags]]

channel flags for forum threads

MISSING
reason Absent[str]

The reason for this change

MISSING

Returns:

Type Description
GuildPublicThread

The edited thread channel object.

Source code in naff/models/discord/channel.py
1911
1912
1913
1914
1915
1916
1917
1918
1919
1920
1921
1922
1923
1924
1925
1926
1927
1928
1929
1930
1931
1932
1933
1934
1935
1936
1937
1938
1939
1940
1941
1942
1943
1944
1945
1946
1947
1948
1949
1950
1951
1952
async def edit(
    self,
    name: Absent[str] = MISSING,
    archived: Absent[bool] = MISSING,
    auto_archive_duration: Absent[AutoArchiveDuration] = MISSING,
    applied_tags: Absent[List[Union[Snowflake_Type, ThreadTag]]] = MISSING,
    locked: Absent[bool] = MISSING,
    rate_limit_per_user: Absent[int] = MISSING,
    flags: Absent[Union[int, ChannelFlags]] = MISSING,
    reason: Absent[str] = MISSING,
    **kwargs,
) -> "GuildPublicThread":
    """
    Edit this thread.

    Args:
        name: 1-100 character channel name
        archived: whether the thread is archived
        applied_tags: list of tags to apply to a forum post (!!! This is for forum threads only)
        auto_archive_duration: duration in minutes to automatically archive the thread after recent activity, can be set to: 60, 1440, 4320, 10080
        locked: whether the thread is locked; when a thread is locked, only users with MANAGE_THREADS can unarchive it
        rate_limit_per_user: amount of seconds a user has to wait before sending another message (0-21600)
        flags: channel flags for forum threads
        reason: The reason for this change

    Returns:
        The edited thread channel object.
    """
    if applied_tags != MISSING:
        applied_tags = [str(tag.id) if isinstance(tag, ThreadTag) else str(tag) for tag in applied_tags]

    return await super().edit(
        name=name,
        archived=archived,
        auto_archive_duration=auto_archive_duration,
        applied_tags=applied_tags,
        locked=locked,
        rate_limit_per_user=rate_limit_per_user,
        reason=reason,
        flags=flags,
        **kwargs,
    )

applied_tags() property

The tags applied to this thread.

Note

This is only on forum threads.

Source code in naff/models/discord/channel.py
1954
1955
1956
1957
1958
1959
1960
1961
1962
1963
1964
1965
@property
def applied_tags(self) -> list[ThreadTag]:
    """
    The tags applied to this thread.

    !!! note
        This is only on forum threads.

    """
    if not isinstance(self.parent_channel, GuildForum):
        raise AttributeError("This is only available on forum threads.")
    return [tag for tag in self.parent_channel.available_tags if str(tag.id) in self._applied_tags]

initial_post() property

The initial message posted by the OP.

Note

This is only on forum threads.

Source code in naff/models/discord/channel.py
1967
1968
1969
1970
1971
1972
1973
1974
1975
1976
1977
1978
@property
def initial_post(self) -> Optional["Message"]:
    """
    The initial message posted by the OP.

    !!! note
        This is only on forum threads.

    """
    if not isinstance(self.parent_channel, GuildForum):
        raise AttributeError("This is only available on forum threads.")
    return self.get_message(self.id)

GuildPrivateThread

Bases: ThreadChannel

Source code in naff/models/discord/channel.py
1981
1982
1983
1984
1985
1986
1987
1988
1989
1990
1991
1992
1993
1994
1995
1996
1997
1998
1999
2000
2001
2002
2003
2004
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
@define()
class GuildPrivateThread(ThreadChannel):
    invitable: bool = field(default=False)
    """Whether non-moderators can add other non-moderators to a thread"""

    async def edit(
        self,
        name: Absent[str] = MISSING,
        archived: Absent[bool] = MISSING,
        auto_archive_duration: Absent[AutoArchiveDuration] = MISSING,
        locked: Absent[bool] = MISSING,
        rate_limit_per_user: Absent[int] = MISSING,
        invitable: Absent[bool] = MISSING,
        reason: Absent[str] = MISSING,
        **kwargs,
    ) -> "GuildPrivateThread":
        """
        Edit this thread.

        Args:
            name: 1-100 character channel name
            archived: whether the thread is archived
            auto_archive_duration: duration in minutes to automatically archive the thread after recent activity, can be set to: 60, 1440, 4320, 10080
            locked: whether the thread is locked; when a thread is locked, only users with MANAGE_THREADS can unarchive it
            rate_limit_per_user: amount of seconds a user has to wait before sending another message (0-21600)
            invitable: whether non-moderators can add other non-moderators to a thread; only available on private threads
            reason: The reason for this change

        Returns:
            The edited thread channel object.

        """
        return await super().edit(
            name=name,
            archived=archived,
            auto_archive_duration=auto_archive_duration,
            locked=locked,
            rate_limit_per_user=rate_limit_per_user,
            invitable=invitable,
            reason=reason,
            **kwargs,
        )

invitable: bool = field(default=False) class-attribute

Whether non-moderators can add other non-moderators to a thread

edit(name=MISSING, archived=MISSING, auto_archive_duration=MISSING, locked=MISSING, rate_limit_per_user=MISSING, invitable=MISSING, reason=MISSING, **kwargs) async

Edit this thread.

Parameters:

Name Type Description Default
name Absent[str]

1-100 character channel name

MISSING
archived Absent[bool]

whether the thread is archived

MISSING
auto_archive_duration Absent[AutoArchiveDuration]

duration in minutes to automatically archive the thread after recent activity, can be set to: 60, 1440, 4320, 10080

MISSING
locked Absent[bool]

whether the thread is locked; when a thread is locked, only users with MANAGE_THREADS can unarchive it

MISSING
rate_limit_per_user Absent[int]

amount of seconds a user has to wait before sending another message (0-21600)

MISSING
invitable Absent[bool]

whether non-moderators can add other non-moderators to a thread; only available on private threads

MISSING
reason Absent[str]

The reason for this change

MISSING

Returns:

Type Description
GuildPrivateThread

The edited thread channel object.

Source code in naff/models/discord/channel.py
1986
1987
1988
1989
1990
1991
1992
1993
1994
1995
1996
1997
1998
1999
2000
2001
2002
2003
2004
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
async def edit(
    self,
    name: Absent[str] = MISSING,
    archived: Absent[bool] = MISSING,
    auto_archive_duration: Absent[AutoArchiveDuration] = MISSING,
    locked: Absent[bool] = MISSING,
    rate_limit_per_user: Absent[int] = MISSING,
    invitable: Absent[bool] = MISSING,
    reason: Absent[str] = MISSING,
    **kwargs,
) -> "GuildPrivateThread":
    """
    Edit this thread.

    Args:
        name: 1-100 character channel name
        archived: whether the thread is archived
        auto_archive_duration: duration in minutes to automatically archive the thread after recent activity, can be set to: 60, 1440, 4320, 10080
        locked: whether the thread is locked; when a thread is locked, only users with MANAGE_THREADS can unarchive it
        rate_limit_per_user: amount of seconds a user has to wait before sending another message (0-21600)
        invitable: whether non-moderators can add other non-moderators to a thread; only available on private threads
        reason: The reason for this change

    Returns:
        The edited thread channel object.

    """
    return await super().edit(
        name=name,
        archived=archived,
        auto_archive_duration=auto_archive_duration,
        locked=locked,
        rate_limit_per_user=rate_limit_per_user,
        invitable=invitable,
        reason=reason,
        **kwargs,
    )

VoiceChannel

Bases: GuildChannel

Source code in naff/models/discord/channel.py
2029
2030
2031
2032
2033
2034
2035
2036
2037
2038
2039
2040
2041
2042
2043
2044
2045
2046
2047
2048
2049
2050
2051
2052
2053
2054
2055
2056
2057
2058
2059
2060
2061
2062
2063
2064
2065
2066
2067
2068
2069
2070
2071
2072
2073
2074
2075
2076
2077
2078
2079
2080
2081
2082
2083
2084
2085
2086
2087
2088
2089
2090
2091
2092
2093
2094
2095
2096
2097
2098
2099
2100
2101
2102
2103
2104
2105
2106
2107
2108
2109
2110
2111
2112
2113
2114
2115
2116
2117
2118
2119
2120
2121
2122
2123
2124
2125
2126
2127
2128
2129
2130
2131
2132
2133
2134
@define(slots=False)
class VoiceChannel(GuildChannel):  # May not be needed, can be directly just GuildVoice.
    bitrate: int = field()
    """The bitrate (in bits) of the voice channel"""
    user_limit: int = field()
    """The user limit of the voice channel"""
    rtc_region: str = field(default="auto")
    """Voice region id for the voice channel, automatic when set to None"""
    video_quality_mode: Union[VideoQualityModes, int] = field(default=VideoQualityModes.AUTO)
    """The camera video quality mode of the voice channel, 1 when not present"""
    _voice_member_ids: list[Snowflake_Type] = field(factory=list)

    async def edit(
        self,
        name: Absent[str] = MISSING,
        position: Absent[int] = MISSING,
        permission_overwrites: Absent[
            Union[dict, PermissionOverwrite, List[Union[dict, PermissionOverwrite]]]
        ] = MISSING,
        parent_id: Absent[Snowflake_Type] = MISSING,
        bitrate: Absent[int] = MISSING,
        user_limit: Absent[int] = MISSING,
        rtc_region: Absent[str] = MISSING,
        video_quality_mode: Absent[VideoQualityModes] = MISSING,
        reason: Absent[str] = MISSING,
        **kwargs,
    ) -> Union["GuildVoice", "GuildStageVoice"]:
        """
        Edit guild voice channel.

        Args:
            name: 1-100 character channel name
            position: the position of the channel in the left-hand listing
            permission_overwrites: a list of `PermissionOverwrite` to apply to the channel
            parent_id: the parent category `Snowflake_Type` for the channel
            bitrate: the bitrate (in bits) of the voice channel; 8000 to 96000 (128000 for VIP servers)
            user_limit: the user limit of the voice channel; 0 refers to no limit, 1 to 99 refers to a user limit
            rtc_region: channel voice region id, automatic when not set
            video_quality_mode: the camera video quality mode of the voice channel
            reason: optional reason for audit logs

        Returns:
            The edited voice channel object.

        """
        return await super().edit(
            name=name,
            position=position,
            permission_overwrites=permission_overwrites,
            parent_id=parent_id,
            bitrate=bitrate,
            user_limit=user_limit,
            rtc_region=rtc_region,
            video_quality_mode=video_quality_mode,
            reason=reason,
            **kwargs,
        )

    @property
    def members(self) -> List["models.Member"]:
        """Returns a list of members that have access to this voice channel"""
        return [m for m in self.guild.members if Permissions.CONNECT in m.channel_permissions(self)]  # type: ignore

    @property
    def voice_members(self) -> List["models.Member"]:
        """
        Returns a list of members that are currently in the channel.

        !!! note
            This will not be accurate if the bot was offline while users joined the channel
        """
        return [self._client.cache.get_member(self._guild_id, member_id) for member_id in self._voice_member_ids]

    @property
    def voice_state(self) -> Optional["ActiveVoiceState"]:
        """Returns the voice state of the bot in this channel if it is connected"""
        return self._client.get_bot_voice_state(self._guild_id)

    async def connect(self, muted: bool = False, deafened: bool = False) -> "ActiveVoiceState":
        """
        Connect the bot to this voice channel, or move the bot to this voice channel if it is already connected in another voice channel.

        Args:
            muted: Whether the bot should be muted when connected.
            deafened: Whether the bot should be deafened when connected.

        Returns:
            The new active voice state on successfully connection.

        """
        if not self.voice_state:
            return await self._client.connect_to_vc(self._guild_id, self.id, muted, deafened)
        await self.voice_state.move(self.id)
        return self.voice_state

    async def disconnect(self) -> None:
        """
        Disconnect from the currently connected voice state.

        Raises:
            VoiceNotConnected: if the bot is not connected to a voice channel
        """
        if self.voice_state:
            return await self.voice_state.disconnect()
        else:
            raise VoiceNotConnected

bitrate: int = field() class-attribute

The bitrate (in bits) of the voice channel

user_limit: int = field() class-attribute

The user limit of the voice channel

rtc_region: str = field(default='auto') class-attribute

Voice region id for the voice channel, automatic when set to None

video_quality_mode: Union[VideoQualityModes, int] = field(default=VideoQualityModes.AUTO) class-attribute

The camera video quality mode of the voice channel, 1 when not present

edit(name=MISSING, position=MISSING, permission_overwrites=MISSING, parent_id=MISSING, bitrate=MISSING, user_limit=MISSING, rtc_region=MISSING, video_quality_mode=MISSING, reason=MISSING, **kwargs) async

Edit guild voice channel.

Parameters:

Name Type Description Default
name Absent[str]

1-100 character channel name

MISSING
position Absent[int]

the position of the channel in the left-hand listing

MISSING
permission_overwrites Absent[Union[dict, PermissionOverwrite, List[Union[dict, PermissionOverwrite]]]]

a list of PermissionOverwrite to apply to the channel

MISSING
parent_id Absent[Snowflake_Type]

the parent category Snowflake_Type for the channel

MISSING
bitrate Absent[int]

the bitrate (in bits) of the voice channel; 8000 to 96000 (128000 for VIP servers)

MISSING
user_limit Absent[int]

the user limit of the voice channel; 0 refers to no limit, 1 to 99 refers to a user limit

MISSING
rtc_region Absent[str]

channel voice region id, automatic when not set

MISSING
video_quality_mode Absent[VideoQualityModes]

the camera video quality mode of the voice channel

MISSING
reason Absent[str]

optional reason for audit logs

MISSING

Returns:

Type Description
Union[GuildVoice, GuildStageVoice]

The edited voice channel object.

Source code in naff/models/discord/channel.py
2041
2042
2043
2044
2045
2046
2047
2048
2049
2050
2051
2052
2053
2054
2055
2056
2057
2058
2059
2060
2061
2062
2063
2064
2065
2066
2067
2068
2069
2070
2071
2072
2073
2074
2075
2076
2077
2078
2079
2080
2081
2082
2083
2084
2085
async def edit(
    self,
    name: Absent[str] = MISSING,
    position: Absent[int] = MISSING,
    permission_overwrites: Absent[
        Union[dict, PermissionOverwrite, List[Union[dict, PermissionOverwrite]]]
    ] = MISSING,
    parent_id: Absent[Snowflake_Type] = MISSING,
    bitrate: Absent[int] = MISSING,
    user_limit: Absent[int] = MISSING,
    rtc_region: Absent[str] = MISSING,
    video_quality_mode: Absent[VideoQualityModes] = MISSING,
    reason: Absent[str] = MISSING,
    **kwargs,
) -> Union["GuildVoice", "GuildStageVoice"]:
    """
    Edit guild voice channel.

    Args:
        name: 1-100 character channel name
        position: the position of the channel in the left-hand listing
        permission_overwrites: a list of `PermissionOverwrite` to apply to the channel
        parent_id: the parent category `Snowflake_Type` for the channel
        bitrate: the bitrate (in bits) of the voice channel; 8000 to 96000 (128000 for VIP servers)
        user_limit: the user limit of the voice channel; 0 refers to no limit, 1 to 99 refers to a user limit
        rtc_region: channel voice region id, automatic when not set
        video_quality_mode: the camera video quality mode of the voice channel
        reason: optional reason for audit logs

    Returns:
        The edited voice channel object.

    """
    return await super().edit(
        name=name,
        position=position,
        permission_overwrites=permission_overwrites,
        parent_id=parent_id,
        bitrate=bitrate,
        user_limit=user_limit,
        rtc_region=rtc_region,
        video_quality_mode=video_quality_mode,
        reason=reason,
        **kwargs,
    )

members() property

Returns a list of members that have access to this voice channel

Source code in naff/models/discord/channel.py
2087
2088
2089
2090
@property
def members(self) -> List["models.Member"]:
    """Returns a list of members that have access to this voice channel"""
    return [m for m in self.guild.members if Permissions.CONNECT in m.channel_permissions(self)]  # type: ignore

voice_members() property

Returns a list of members that are currently in the channel.

Note

This will not be accurate if the bot was offline while users joined the channel

Source code in naff/models/discord/channel.py
2092
2093
2094
2095
2096
2097
2098
2099
2100
@property
def voice_members(self) -> List["models.Member"]:
    """
    Returns a list of members that are currently in the channel.

    !!! note
        This will not be accurate if the bot was offline while users joined the channel
    """
    return [self._client.cache.get_member(self._guild_id, member_id) for member_id in self._voice_member_ids]

voice_state() property

Returns the voice state of the bot in this channel if it is connected

Source code in naff/models/discord/channel.py
2102
2103
2104
2105
@property
def voice_state(self) -> Optional["ActiveVoiceState"]:
    """Returns the voice state of the bot in this channel if it is connected"""
    return self._client.get_bot_voice_state(self._guild_id)

connect(muted=False, deafened=False) async

Connect the bot to this voice channel, or move the bot to this voice channel if it is already connected in another voice channel.

Parameters:

Name Type Description Default
muted bool

Whether the bot should be muted when connected.

False
deafened bool

Whether the bot should be deafened when connected.

False

Returns:

Type Description
ActiveVoiceState

The new active voice state on successfully connection.

Source code in naff/models/discord/channel.py
2107
2108
2109
2110
2111
2112
2113
2114
2115
2116
2117
2118
2119
2120
2121
2122
async def connect(self, muted: bool = False, deafened: bool = False) -> "ActiveVoiceState":
    """
    Connect the bot to this voice channel, or move the bot to this voice channel if it is already connected in another voice channel.

    Args:
        muted: Whether the bot should be muted when connected.
        deafened: Whether the bot should be deafened when connected.

    Returns:
        The new active voice state on successfully connection.

    """
    if not self.voice_state:
        return await self._client.connect_to_vc(self._guild_id, self.id, muted, deafened)
    await self.voice_state.move(self.id)
    return self.voice_state

disconnect() async

Disconnect from the currently connected voice state.

Raises:

Type Description
VoiceNotConnected

if the bot is not connected to a voice channel

Source code in naff/models/discord/channel.py
2124
2125
2126
2127
2128
2129
2130
2131
2132
2133
2134
async def disconnect(self) -> None:
    """
    Disconnect from the currently connected voice state.

    Raises:
        VoiceNotConnected: if the bot is not connected to a voice channel
    """
    if self.voice_state:
        return await self.voice_state.disconnect()
    else:
        raise VoiceNotConnected

GuildStageVoice

Bases: GuildVoice

Source code in naff/models/discord/channel.py
2142
2143
2144
2145
2146
2147
2148
2149
2150
2151
2152
2153
2154
2155
2156
2157
2158
2159
2160
2161
2162
2163
2164
2165
2166
2167
2168
2169
2170
2171
2172
2173
2174
2175
2176
2177
2178
2179
2180
2181
2182
2183
2184
2185
2186
2187
2188
2189
2190
2191
2192
2193
2194
2195
2196
2197
2198
@define()
class GuildStageVoice(GuildVoice):
    stage_instance: "models.StageInstance" = field(default=MISSING)
    """The stage instance that this voice channel belongs to"""

    # todo: Listeners and speakers properties (needs voice state caching)

    async def fetch_stage_instance(self) -> "models.StageInstance":
        """
        Fetches the stage instance associated with this channel.

        Returns:
            The stage instance associated with this channel. If no stage is live, will return None.

        """
        self.stage_instance = models.StageInstance.from_dict(
            await self._client.http.get_stage_instance(self.id), self._client
        )
        return self.stage_instance

    async def create_stage_instance(
        self,
        topic: str,
        privacy_level: StagePrivacyLevel = StagePrivacyLevel.GUILD_ONLY,
        reason: Absent[Optional[str]] = MISSING,
    ) -> "models.StageInstance":
        """
        Create a stage instance in this channel.

        Args:
            topic: The topic of the stage (1-120 characters)
            privacy_level: The privacy level of the stage
            reason: The reason for creating this instance

        Returns:
            The created stage instance object.

        """
        self.stage_instance = models.StageInstance.from_dict(
            await self._client.http.create_stage_instance(self.id, topic, privacy_level, reason), self._client
        )
        return self.stage_instance

    async def close_stage(self, reason: Absent[Optional[str]] = MISSING) -> None:
        """
        Closes the live stage instance.

        Args:
            reason: The reason for closing the stage

        """
        if not self.stage_instance:
            # we dont know of an active stage instance, so lets check for one
            if not await self.get_stage_instance():
                raise ValueError("No stage instance found")

        await self.stage_instance.delete(reason=reason)

stage_instance: models.StageInstance = field(default=MISSING) class-attribute

The stage instance that this voice channel belongs to

fetch_stage_instance() async

Fetches the stage instance associated with this channel.

Returns:

Type Description
StageInstance

The stage instance associated with this channel. If no stage is live, will return None.

Source code in naff/models/discord/channel.py
2149
2150
2151
2152
2153
2154
2155
2156
2157
2158
2159
2160
async def fetch_stage_instance(self) -> "models.StageInstance":
    """
    Fetches the stage instance associated with this channel.

    Returns:
        The stage instance associated with this channel. If no stage is live, will return None.

    """
    self.stage_instance = models.StageInstance.from_dict(
        await self._client.http.get_stage_instance(self.id), self._client
    )
    return self.stage_instance

create_stage_instance(topic, privacy_level=StagePrivacyLevel.GUILD_ONLY, reason=MISSING) async

Create a stage instance in this channel.

Parameters:

Name Type Description Default
topic str

The topic of the stage (1-120 characters)

required
privacy_level StagePrivacyLevel

The privacy level of the stage

StagePrivacyLevel.GUILD_ONLY
reason Absent[Optional[str]]

The reason for creating this instance

MISSING

Returns:

Type Description
StageInstance

The created stage instance object.

Source code in naff/models/discord/channel.py
2162
2163
2164
2165
2166
2167
2168
2169
2170
2171
2172
2173
2174
2175
2176
2177
2178
2179
2180
2181
2182
2183
async def create_stage_instance(
    self,
    topic: str,
    privacy_level: StagePrivacyLevel = StagePrivacyLevel.GUILD_ONLY,
    reason: Absent[Optional[str]] = MISSING,
) -> "models.StageInstance":
    """
    Create a stage instance in this channel.

    Args:
        topic: The topic of the stage (1-120 characters)
        privacy_level: The privacy level of the stage
        reason: The reason for creating this instance

    Returns:
        The created stage instance object.

    """
    self.stage_instance = models.StageInstance.from_dict(
        await self._client.http.create_stage_instance(self.id, topic, privacy_level, reason), self._client
    )
    return self.stage_instance

close_stage(reason=MISSING) async

Closes the live stage instance.

Parameters:

Name Type Description Default
reason Absent[Optional[str]]

The reason for closing the stage

MISSING
Source code in naff/models/discord/channel.py
2185
2186
2187
2188
2189
2190
2191
2192
2193
2194
2195
2196
2197
2198
async def close_stage(self, reason: Absent[Optional[str]] = MISSING) -> None:
    """
    Closes the live stage instance.

    Args:
        reason: The reason for closing the stage

    """
    if not self.stage_instance:
        # we dont know of an active stage instance, so lets check for one
        if not await self.get_stage_instance():
            raise ValueError("No stage instance found")

    await self.stage_instance.delete(reason=reason)

GuildForum

Bases: GuildChannel

Source code in naff/models/discord/channel.py
2201
2202
2203
2204
2205
2206
2207
2208
2209
2210
2211
2212
2213
2214
2215
2216
2217
2218
2219
2220
2221
2222
2223
2224
2225
2226
2227
2228
2229
2230
2231
2232
2233
2234
2235
2236
2237
2238
2239
2240
2241
2242
2243
2244
2245
2246
2247
2248
2249
2250
2251
2252
2253
2254
2255
2256
2257
2258
2259
2260
2261
2262
2263
2264
2265
2266
2267
2268
2269
2270
2271
2272
2273
2274
2275
2276
2277
2278
2279
2280
2281
2282
2283
2284
2285
2286
2287
2288
2289
2290
2291
2292
2293
2294
2295
2296
2297
2298
2299
2300
2301
2302
2303
2304
2305
2306
2307
2308
2309
2310
2311
2312
2313
2314
2315
2316
2317
2318
2319
2320
2321
2322
2323
2324
2325
2326
2327
2328
2329
2330
2331
2332
2333
2334
2335
2336
2337
2338
2339
2340
2341
2342
2343
2344
2345
2346
2347
@define()
class GuildForum(GuildChannel):
    available_tags: List[ThreadTag] = field(factory=list)
    """A list of tags available to assign to threads"""
    last_message_id: Optional[Snowflake_Type] = field(default=None)
    # TODO: Implement "template" once the API supports them

    @classmethod
    def _process_dict(cls, data: Dict[str, Any], client: "Client") -> Dict[str, Any]:
        data = super()._process_dict(data, client)
        data["available_tags"] = [
            ThreadTag.from_dict(tag_data | {"parent_channel_id": data["id"]}, client)
            for tag_data in data.get("available_tags", [])
        ]
        return data

    async def create_post(
        self,
        name: str,
        content: str | None,
        applied_tags: Optional[List[Union["Snowflake_Type", "ThreadTag"]]] = MISSING,
        *,
        auto_archive_duration: AutoArchiveDuration = AutoArchiveDuration.ONE_DAY,
        rate_limit_per_user: Absent[int] = MISSING,
        embeds: Optional[Union[List[Union["Embed", dict]], Union["Embed", dict]]] = None,
        embed: Optional[Union["Embed", dict]] = None,
        components: Optional[
            Union[List[List[Union["BaseComponent", dict]]], List[Union["BaseComponent", dict]], "BaseComponent", dict]
        ] = None,
        stickers: Optional[Union[List[Union["Sticker", "Snowflake_Type"]], "Sticker", "Snowflake_Type"]] = None,
        allowed_mentions: Optional[Union["AllowedMentions", dict]] = None,
        files: Optional[Union["UPLOADABLE_TYPE", List["UPLOADABLE_TYPE"]]] = None,
        file: Optional["UPLOADABLE_TYPE"] = None,
        tts: bool = False,
        reason: Absent[str] = MISSING,
    ) -> "GuildPublicThread":
        """
        Create a post within this channel.

        Args:
            name: The name of the post
            content: The text content of this post
            applied_tags: A list of tag ids or tag objects to apply to this post
            auto_archive_duration: Time before the thread will be automatically archived. Note 3 day and 7 day archive durations require the server to be boosted.
            rate_limit_per_user: The time users must wait between sending messages
            embeds: Embedded rich content (up to 6000 characters).
            embed: Embedded rich content (up to 6000 characters).
            components: The components to include with the message.
            stickers: IDs of up to 3 stickers in the server to send in the message.
            allowed_mentions: Allowed mentions for the message.
            files: Files to send, the path, bytes or File() instance, defaults to None. You may have up to 10 files.
            file: Files to send, the path, bytes or File() instance, defaults to None. You may have up to 10 files.
            tts: Should this message use Text To Speech.
            reason: The reason for creating this post

        Returns:
            A GuildPublicThread object representing the created post.
        """
        if applied_tags != MISSING:
            applied_tags = [str(tag.id) if isinstance(tag, ThreadTag) else str(tag) for tag in applied_tags]

        message_payload = models.discord.message.process_message_payload(
            content=content,
            embeds=embeds or embed,
            components=components,
            stickers=stickers,
            allowed_mentions=allowed_mentions,
            tts=tts,
        )

        data = await self._client.http.create_forum_thread(
            self.id,
            name,
            auto_archive_duration,
            message_payload,
            applied_tags,
            rate_limit_per_user,
            files=files or file,
            reason=reason,
        )
        return self._client.cache.place_channel_data(data)

    async def create_tag(self, name: str, emoji: Union["models.PartialEmoji", dict, str]) -> "ThreadTag":
        """
        Create a tag for this forum.

        Args:
            name: The name of the tag
            emoji: The emoji to use for the tag

        !!! note
            If the emoji is a custom emoji, it must be from the same guild as the channel.

        Returns:
            The created tag object.

        """
        if isinstance(emoji, str):
            emoji = PartialEmoji.from_str(emoji)
        elif isinstance(emoji, dict):
            emoji = PartialEmoji.from_dict(emoji)

        if emoji.id:
            data = await self._client.http.create_tag(self.id, name, emoji_id=emoji.id)
        else:
            data = await self._client.http.create_tag(self.id, name, emoji_name=emoji.name)

        channel_data = self._client.cache.place_channel_data(data)
        return [tag for tag in channel_data.available_tags if tag.name == name][0]

    async def edit_tag(
        self,
        tag_id: "Snowflake_Type",
        *,
        name: str | None = None,
        emoji: Union["models.PartialEmoji", dict, str, None] = None,
    ) -> "ThreadTag":
        """
        Edit a tag for this forum.

        Args:
            tag_id: The id of the tag to edit
            name: The name for this tag
            emoji: The emoji for this tag
        """
        if isinstance(emoji, str):
            emoji = PartialEmoji.from_str(emoji)
        elif isinstance(emoji, dict):
            emoji = PartialEmoji.from_dict(emoji)

        if emoji.id:
            data = await self._client.http.edit_tag(self.id, tag_id, name, emoji_id=emoji.id)
        else:
            data = await self._client.http.edit_tag(self.id, tag_id, name, emoji_name=emoji.name)

        channel_data = self._client.cache.place_channel_data(data)
        return [tag for tag in channel_data.available_tags if tag.name == name][0]

    async def delete_tag(self, tag_id: "Snowflake_Type") -> None:
        """
        Delete a tag for this forum.

        Args:
            tag_id: The ID of the tag to delete
        """
        data = await self._client.http.delete_tag(self.id, tag_id)
        self._client.cache.place_channel_data(data)

available_tags: List[ThreadTag] = field(factory=list) class-attribute

A list of tags available to assign to threads

create_post(name, content, applied_tags=MISSING, *, auto_archive_duration=AutoArchiveDuration.ONE_DAY, rate_limit_per_user=MISSING, embeds=None, embed=None, components=None, stickers=None, allowed_mentions=None, files=None, file=None, tts=False, reason=MISSING) async

Create a post within this channel.

Parameters:

Name Type Description Default
name str

The name of the post

required
content str | None

The text content of this post

required
applied_tags Optional[List[Union[Snowflake_Type, ThreadTag]]]

A list of tag ids or tag objects to apply to this post

MISSING
auto_archive_duration AutoArchiveDuration

Time before the thread will be automatically archived. Note 3 day and 7 day archive durations require the server to be boosted.

AutoArchiveDuration.ONE_DAY
rate_limit_per_user Absent[int]

The time users must wait between sending messages

MISSING
embeds Optional[Union[List[Union[Embed, dict]], Union[Embed, dict]]]

Embedded rich content (up to 6000 characters).

None
embed Optional[Union[Embed, dict]]

Embedded rich content (up to 6000 characters).

None
components Optional[Union[List[List[Union[BaseComponent, dict]]], List[Union[BaseComponent, dict]], BaseComponent, dict]]

The components to include with the message.

None
stickers Optional[Union[List[Union[Sticker, Snowflake_Type]], Sticker, Snowflake_Type]]

IDs of up to 3 stickers in the server to send in the message.

None
allowed_mentions Optional[Union[AllowedMentions, dict]]

Allowed mentions for the message.

None
files Optional[Union[UPLOADABLE_TYPE, List[UPLOADABLE_TYPE]]]

Files to send, the path, bytes or File() instance, defaults to None. You may have up to 10 files.

None
file Optional[UPLOADABLE_TYPE]

Files to send, the path, bytes or File() instance, defaults to None. You may have up to 10 files.

None
tts bool

Should this message use Text To Speech.

False
reason Absent[str]

The reason for creating this post

MISSING

Returns:

Type Description
GuildPublicThread

A GuildPublicThread object representing the created post.

Source code in naff/models/discord/channel.py
2217
2218
2219
2220
2221
2222
2223
2224
2225
2226
2227
2228
2229
2230
2231
2232
2233
2234
2235
2236
2237
2238
2239
2240
2241
2242
2243
2244
2245
2246
2247
2248
2249
2250
2251
2252
2253
2254
2255
2256
2257
2258
2259
2260
2261
2262
2263
2264
2265
2266
2267
2268
2269
2270
2271
2272
2273
2274
2275
2276
2277
2278
2279
2280
2281
async def create_post(
    self,
    name: str,
    content: str | None,
    applied_tags: Optional[List[Union["Snowflake_Type", "ThreadTag"]]] = MISSING,
    *,
    auto_archive_duration: AutoArchiveDuration = AutoArchiveDuration.ONE_DAY,
    rate_limit_per_user: Absent[int] = MISSING,
    embeds: Optional[Union[List[Union["Embed", dict]], Union["Embed", dict]]] = None,
    embed: Optional[Union["Embed", dict]] = None,
    components: Optional[
        Union[List[List[Union["BaseComponent", dict]]], List[Union["BaseComponent", dict]], "BaseComponent", dict]
    ] = None,
    stickers: Optional[Union[List[Union["Sticker", "Snowflake_Type"]], "Sticker", "Snowflake_Type"]] = None,
    allowed_mentions: Optional[Union["AllowedMentions", dict]] = None,
    files: Optional[Union["UPLOADABLE_TYPE", List["UPLOADABLE_TYPE"]]] = None,
    file: Optional["UPLOADABLE_TYPE"] = None,
    tts: bool = False,
    reason: Absent[str] = MISSING,
) -> "GuildPublicThread":
    """
    Create a post within this channel.

    Args:
        name: The name of the post
        content: The text content of this post
        applied_tags: A list of tag ids or tag objects to apply to this post
        auto_archive_duration: Time before the thread will be automatically archived. Note 3 day and 7 day archive durations require the server to be boosted.
        rate_limit_per_user: The time users must wait between sending messages
        embeds: Embedded rich content (up to 6000 characters).
        embed: Embedded rich content (up to 6000 characters).
        components: The components to include with the message.
        stickers: IDs of up to 3 stickers in the server to send in the message.
        allowed_mentions: Allowed mentions for the message.
        files: Files to send, the path, bytes or File() instance, defaults to None. You may have up to 10 files.
        file: Files to send, the path, bytes or File() instance, defaults to None. You may have up to 10 files.
        tts: Should this message use Text To Speech.
        reason: The reason for creating this post

    Returns:
        A GuildPublicThread object representing the created post.
    """
    if applied_tags != MISSING:
        applied_tags = [str(tag.id) if isinstance(tag, ThreadTag) else str(tag) for tag in applied_tags]

    message_payload = models.discord.message.process_message_payload(
        content=content,
        embeds=embeds or embed,
        components=components,
        stickers=stickers,
        allowed_mentions=allowed_mentions,
        tts=tts,
    )

    data = await self._client.http.create_forum_thread(
        self.id,
        name,
        auto_archive_duration,
        message_payload,
        applied_tags,
        rate_limit_per_user,
        files=files or file,
        reason=reason,
    )
    return self._client.cache.place_channel_data(data)

create_tag(name, emoji) async

Create a tag for this forum.

Parameters:

Name Type Description Default
name str

The name of the tag

required
emoji Union[PartialEmoji, dict, str]

The emoji to use for the tag

required

Note

If the emoji is a custom emoji, it must be from the same guild as the channel.

Returns:

Type Description
ThreadTag

The created tag object.

Source code in naff/models/discord/channel.py
2283
2284
2285
2286
2287
2288
2289
2290
2291
2292
2293
2294
2295
2296
2297
2298
2299
2300
2301
2302
2303
2304
2305
2306
2307
2308
2309
async def create_tag(self, name: str, emoji: Union["models.PartialEmoji", dict, str]) -> "ThreadTag":
    """
    Create a tag for this forum.

    Args:
        name: The name of the tag
        emoji: The emoji to use for the tag

    !!! note
        If the emoji is a custom emoji, it must be from the same guild as the channel.

    Returns:
        The created tag object.

    """
    if isinstance(emoji, str):
        emoji = PartialEmoji.from_str(emoji)
    elif isinstance(emoji, dict):
        emoji = PartialEmoji.from_dict(emoji)

    if emoji.id:
        data = await self._client.http.create_tag(self.id, name, emoji_id=emoji.id)
    else:
        data = await self._client.http.create_tag(self.id, name, emoji_name=emoji.name)

    channel_data = self._client.cache.place_channel_data(data)
    return [tag for tag in channel_data.available_tags if tag.name == name][0]

edit_tag(tag_id, *, name=None, emoji=None) async

Edit a tag for this forum.

Parameters:

Name Type Description Default
tag_id Snowflake_Type

The id of the tag to edit

required
name str | None

The name for this tag

None
emoji Union[PartialEmoji, dict, str, None]

The emoji for this tag

None
Source code in naff/models/discord/channel.py
2311
2312
2313
2314
2315
2316
2317
2318
2319
2320
2321
2322
2323
2324
2325
2326
2327
2328
2329
2330
2331
2332
2333
2334
2335
2336
2337
async def edit_tag(
    self,
    tag_id: "Snowflake_Type",
    *,
    name: str | None = None,
    emoji: Union["models.PartialEmoji", dict, str, None] = None,
) -> "ThreadTag":
    """
    Edit a tag for this forum.

    Args:
        tag_id: The id of the tag to edit
        name: The name for this tag
        emoji: The emoji for this tag
    """
    if isinstance(emoji, str):
        emoji = PartialEmoji.from_str(emoji)
    elif isinstance(emoji, dict):
        emoji = PartialEmoji.from_dict(emoji)

    if emoji.id:
        data = await self._client.http.edit_tag(self.id, tag_id, name, emoji_id=emoji.id)
    else:
        data = await self._client.http.edit_tag(self.id, tag_id, name, emoji_name=emoji.name)

    channel_data = self._client.cache.place_channel_data(data)
    return [tag for tag in channel_data.available_tags if tag.name == name][0]

delete_tag(tag_id) async

Delete a tag for this forum.

Parameters:

Name Type Description Default
tag_id Snowflake_Type

The ID of the tag to delete

required
Source code in naff/models/discord/channel.py
2339
2340
2341
2342
2343
2344
2345
2346
2347
async def delete_tag(self, tag_id: "Snowflake_Type") -> None:
    """
    Delete a tag for this forum.

    Args:
        tag_id: The ID of the tag to delete
    """
    data = await self._client.http.delete_tag(self.id, tag_id)
    self._client.cache.place_channel_data(data)

process_permission_overwrites(overwrites)

Processes a permission overwrite lists into format for sending to discord.

Parameters:

Name Type Description Default
overwrites Union[dict, PermissionOverwrite, List[Union[dict, PermissionOverwrite]]]

The permission overwrites to process

required

Returns:

Type Description
List[dict]

The processed permission overwrites

Source code in naff/models/discord/channel.py
2350
2351
2352
2353
2354
2355
2356
2357
2358
2359
2360
2361
2362
2363
2364
2365
2366
2367
2368
2369
2370
2371
2372
2373
2374
2375
def process_permission_overwrites(
    overwrites: Union[dict, PermissionOverwrite, List[Union[dict, PermissionOverwrite]]]
) -> List[dict]:
    """
    Processes a permission overwrite lists into format for sending to discord.

    Args:
        overwrites: The permission overwrites to process

    Returns:
        The processed permission overwrites

    """
    if not overwrites:
        return overwrites

    if isinstance(overwrites, dict):
        return [overwrites]

    if isinstance(overwrites, list):
        return list(map(to_dict, overwrites))

    if isinstance(overwrites, PermissionOverwrite):
        return [overwrites.to_dict()]

    raise ValueError(f"Invalid overwrites: {overwrites}")

Invite

Bases: ClientObject

Source code in naff/models/discord/invite.py
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
@define()
class Invite(ClientObject):
    code: str = field(repr=True)
    """the invite code (unique ID)"""

    # metadata
    uses: int = field(default=0, repr=True)
    """the guild this invite is for"""
    max_uses: int = field(default=0)
    """max number of times this invite can be used"""
    max_age: int = field(default=0)
    """duration (in seconds) after which the invite expires"""
    created_at: Timestamp = field(default=MISSING, converter=optional_c(timestamp_converter), repr=True)
    """when this invite was created"""
    temporary: bool = field(default=False, repr=True)
    """whether this invite only grants temporary membership"""

    # target data
    target_type: Optional[Union[InviteTargetTypes, int]] = field(
        default=None, converter=optional_c(InviteTargetTypes), repr=True
    )
    """the type of target for this voice channel invite"""
    approximate_presence_count: Optional[int] = field(default=MISSING)
    """approximate count of online members, returned from the `GET /invites/<code>` endpoint when `with_counts` is `True`"""
    approximate_member_count: Optional[int] = field(default=MISSING)
    """approximate count of total members, returned from the `GET /invites/<code>` endpoint when `with_counts` is `True`"""
    scheduled_event: Optional["Snowflake_Type"] = field(default=None, converter=optional_c(to_snowflake), repr=True)
    """guild scheduled event data, only included if `guild_scheduled_event_id` contains a valid guild scheduled event id"""
    expires_at: Optional[Timestamp] = field(default=None, converter=optional_c(timestamp_converter), repr=True)
    """the expiration date of this invite, returned from the `GET /invites/<code>` endpoint when `with_expiration` is `True`"""
    stage_instance: Optional[StageInstance] = field(default=None)
    """stage instance data if there is a public Stage instance in the Stage channel this invite is for (deprecated)"""
    target_application: Optional[dict] = field(default=None)
    """the embedded application to open for this voice channel embedded application invite"""
    guild_preview: Optional[GuildPreview] = field(default=MISSING)
    """the guild this invite is for"""

    # internal for props
    _channel_id: "Snowflake_Type" = field(converter=to_snowflake, repr=True)
    _inviter_id: Optional["Snowflake_Type"] = field(default=None, converter=optional_c(to_snowflake), repr=True)
    _target_user_id: Optional["Snowflake_Type"] = field(default=None, converter=optional_c(to_snowflake))

    @property
    def channel(self) -> "TYPE_GUILD_CHANNEL":
        """The channel the invite is for."""
        return self._client.cache.get_channel(self._channel_id)

    @property
    def inviter(self) -> Optional["User"]:
        """The user that created the invite or None."""
        return self._client.cache.get_user(self._inviter_id) if self._inviter_id else None

    @property
    def target_user(self) -> Optional["User"]:
        """The user whose stream to display for this voice channel stream invite or None."""
        return self._client.cache.get_user(self._target_user_id) if self._target_user_id else None

    @classmethod
    def _process_dict(cls, data: Dict[str, Any], client: "Client") -> Dict[str, Any]:
        if "stage_instance" in data:
            data["stage_instance"] = StageInstance.from_dict(data, client)

        if "target_application" in data:
            data["target_application"] = Application.from_dict(data, client)

        if "target_event_id" in data:
            data["scheduled_event"] = data["target_event_id"]

        if channel := data.pop("channel", None):
            # invite metadata does not contain enough info to create a channel object
            data["channel_id"] = channel["id"]

        if guild := data.pop("guild", None):
            data["guild_preview"] = GuildPreview.from_dict(guild, client)

        if inviter := data.pop("inviter", None):
            inviter = client.cache.place_user_data(inviter)
            data["inviter_id"] = inviter.id

        return data

    def __str__(self) -> str:
        return self.link

    @property
    def link(self) -> str:
        """The invite link."""
        if self.scheduled_event:
            return f"https://discord.gg/{self.code}?event={self.scheduled_event}"
        return f"https://discord.gg/{self.code}"

    async def delete(self, reason: Absent[str] = MISSING) -> None:
        """
        Delete this invite.

        !!! note
            You must have the `manage_channels` permission on the channel this invite belongs to.

        !!! note
            With `manage_guild` permission, you can delete any invite across the guild.

        Args:
            reason: The reason for the deletion of invite.

        """
        await self._client.http.delete_invite(self.code, reason=reason)

code: str = field(repr=True) class-attribute

the invite code (unique ID)

uses: int = field(default=0, repr=True) class-attribute

the guild this invite is for

max_uses: int = field(default=0) class-attribute

max number of times this invite can be used

max_age: int = field(default=0) class-attribute

duration (in seconds) after which the invite expires

created_at: Timestamp = field(default=MISSING, converter=optional_c(timestamp_converter), repr=True) class-attribute

when this invite was created

temporary: bool = field(default=False, repr=True) class-attribute

whether this invite only grants temporary membership

target_type: Optional[Union[InviteTargetTypes, int]] = field(default=None, converter=optional_c(InviteTargetTypes), repr=True) class-attribute

the type of target for this voice channel invite

approximate_presence_count: Optional[int] = field(default=MISSING) class-attribute

approximate count of online members, returned from the GET /invites/<code> endpoint when with_counts is True

approximate_member_count: Optional[int] = field(default=MISSING) class-attribute

approximate count of total members, returned from the GET /invites/<code> endpoint when with_counts is True

scheduled_event: Optional[Snowflake_Type] = field(default=None, converter=optional_c(to_snowflake), repr=True) class-attribute

guild scheduled event data, only included if guild_scheduled_event_id contains a valid guild scheduled event id

expires_at: Optional[Timestamp] = field(default=None, converter=optional_c(timestamp_converter), repr=True) class-attribute

the expiration date of this invite, returned from the GET /invites/<code> endpoint when with_expiration is True

stage_instance: Optional[StageInstance] = field(default=None) class-attribute

stage instance data if there is a public Stage instance in the Stage channel this invite is for (deprecated)

target_application: Optional[dict] = field(default=None) class-attribute

the embedded application to open for this voice channel embedded application invite

guild_preview: Optional[GuildPreview] = field(default=MISSING) class-attribute

the guild this invite is for

channel() property

The channel the invite is for.

Source code in naff/models/discord/invite.py
66
67
68
69
@property
def channel(self) -> "TYPE_GUILD_CHANNEL":
    """The channel the invite is for."""
    return self._client.cache.get_channel(self._channel_id)

inviter() property

The user that created the invite or None.

Source code in naff/models/discord/invite.py
71
72
73
74
@property
def inviter(self) -> Optional["User"]:
    """The user that created the invite or None."""
    return self._client.cache.get_user(self._inviter_id) if self._inviter_id else None

target_user() property

The user whose stream to display for this voice channel stream invite or None.

Source code in naff/models/discord/invite.py
76
77
78
79
@property
def target_user(self) -> Optional["User"]:
    """The user whose stream to display for this voice channel stream invite or None."""
    return self._client.cache.get_user(self._target_user_id) if self._target_user_id else None

The invite link.

Source code in naff/models/discord/invite.py
108
109
110
111
112
113
@property
def link(self) -> str:
    """The invite link."""
    if self.scheduled_event:
        return f"https://discord.gg/{self.code}?event={self.scheduled_event}"
    return f"https://discord.gg/{self.code}"

delete(reason=MISSING) async

Delete this invite.

Note

You must have the manage_channels permission on the channel this invite belongs to.

Note

With manage_guild permission, you can delete any invite across the guild.

Parameters:

Name Type Description Default
reason Absent[str]

The reason for the deletion of invite.

MISSING
Source code in naff/models/discord/invite.py
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
async def delete(self, reason: Absent[str] = MISSING) -> None:
    """
    Delete this invite.

    !!! note
        You must have the `manage_channels` permission on the channel this invite belongs to.

    !!! note
        With `manage_guild` permission, you can delete any invite across the guild.

    Args:
        reason: The reason for the deletion of invite.

    """
    await self._client.http.delete_invite(self.code, reason=reason)

Role

Bases: DiscordObject

Source code in naff/models/discord/role.py
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
@define()
@total_ordering
class Role(DiscordObject):
    _sentinel = object()

    name: str = field(repr=True)
    color: "Color" = field(converter=Color)
    hoist: bool = field(default=False)
    position: int = field(repr=True)
    permissions: "Permissions" = field(converter=Permissions)
    managed: bool = field(default=False)
    mentionable: bool = field(default=True)
    premium_subscriber: bool = field(default=_sentinel, converter=partial(sentinel_converter, sentinel=_sentinel))
    _icon: Asset | None = field(default=None)
    _unicode_emoji: PartialEmoji | None = field(default=None, converter=optional_c(PartialEmoji.from_str))
    _guild_id: "Snowflake_Type" = field()
    _bot_id: "Snowflake_Type | None" = field(default=None)
    _integration_id: "Snowflake_Type | None" = field(default=None)  # todo integration object?

    def __lt__(self: "Role", other: "Role") -> bool:
        if not isinstance(self, Role) or not isinstance(other, Role):
            return NotImplemented

        if self._guild_id != other._guild_id:
            raise RuntimeError("Unable to compare Roles from different guilds.")

        if self.id == self._guild_id:  # everyone role
            # everyone role is on the bottom, so check if the other role is, well, not it
            # because then it must be higher than it
            return other.id != self.id

        if self.position < other.position:
            return True

        if self.position == other.position:
            # if two roles have the same position, which can happen thanks to discord, then
            # we can thankfully use their ids to determine which one is lower
            return self.id < other.id

        return False

    @classmethod
    def _process_dict(cls, data: dict[str, Any], client: "Client") -> dict[str, Any]:
        data.update(data.pop("tags", {}))

        if icon_hash := data.get("icon"):
            data["icon"] = Asset.from_path_hash(client, f"role-icons/{data['id']}/{{}}", icon_hash)

        return data

    async def fetch_bot(self) -> "Member | None":
        """
        Fetch the bot associated with this role if any.

        Returns:
            Member object if any

        """
        if self._bot_id is None:
            return None
        return await self._client.cache.fetch_member(self._guild_id, self._bot_id)

    def get_bot(self) -> "Member | None":
        """
        Get the bot associated with this role if any.

        Returns:
            Member object if any

        """
        if self._bot_id is None:
            return None
        return self._client.cache.get_member(self._guild_id, self._bot_id)

    @property
    def guild(self) -> "Guild":
        """The guild object this role is from."""
        return self._client.cache.get_guild(self._guild_id)  # pyright: ignore [reportGeneralTypeIssues]

    @property
    def default(self) -> bool:
        """Is this the `@everyone` role."""
        return self.id == self._guild_id

    @property
    def bot_managed(self) -> bool:
        """Is this role owned/managed by a bot."""
        return self._bot_id is not None

    @property
    def mention(self) -> str:
        """Returns a string that would mention the role."""
        return f"<@&{self.id}>" if self.id != self._guild_id else "@everyone"

    @property
    def integration(self) -> bool:
        """Is this role owned/managed by an integration."""
        return self._integration_id is not None

    @property
    def members(self) -> list["Member"]:
        """List of members with this role"""
        return [member for member in self.guild.members if member.has_role(self)]

    @property
    def icon(self) -> Asset | PartialEmoji | None:
        """
        The icon of this role

        !!! note
            You have to use this method instead of the `_icon` attribute, because the first does account for unicode emojis
        """
        return self._icon or self._unicode_emoji

    @property
    def is_assignable(self) -> bool:
        """
        Can this role be assigned or removed by this bot?

        !!! note
            This does not account for permissions, only the role hierarchy

        """
        return (self.default or self.guild.me.top_role > self) and not self.managed

    async def delete(self, reason: str | Missing = MISSING) -> None:
        """
        Delete this role.

        Args:
            reason: An optional reason for this deletion

        """
        await self._client.http.delete_guild_role(self._guild_id, self.id, reason)

    async def edit(
        self,
        name: str | None = None,
        permissions: str | None = None,
        color: Color | COLOR_TYPES | None = None,
        hoist: bool | None = None,
        mentionable: bool | None = None,
    ) -> "Role":
        """
        Edit this role, all arguments are optional.

        Args:
            name: name of the role
            permissions: New permissions to use
            color: The color of the role
            hoist: whether the role should be displayed separately in the sidebar
            mentionable: whether the role should be mentionable

        Returns:
            Role with updated information

        """
        color = process_color(color)

        payload = dict_filter(
            {"name": name, "permissions": permissions, "color": color, "hoist": hoist, "mentionable": mentionable}
        )

        r_data = await self._client.http.modify_guild_role(self._guild_id, self.id, payload)
        r_data = dict(r_data)  # to convert typed dict to regular dict
        r_data["guild_id"] = self._guild_id
        return self.from_dict(r_data, self._client)

fetch_bot() async

Fetch the bot associated with this role if any.

Returns:

Type Description
Member | None

Member object if any

Source code in naff/models/discord/role.py
83
84
85
86
87
88
89
90
91
92
93
async def fetch_bot(self) -> "Member | None":
    """
    Fetch the bot associated with this role if any.

    Returns:
        Member object if any

    """
    if self._bot_id is None:
        return None
    return await self._client.cache.fetch_member(self._guild_id, self._bot_id)

get_bot()

Get the bot associated with this role if any.

Returns:

Type Description
Member | None

Member object if any

Source code in naff/models/discord/role.py
 95
 96
 97
 98
 99
100
101
102
103
104
105
def get_bot(self) -> "Member | None":
    """
    Get the bot associated with this role if any.

    Returns:
        Member object if any

    """
    if self._bot_id is None:
        return None
    return self._client.cache.get_member(self._guild_id, self._bot_id)

guild() property

The guild object this role is from.

Source code in naff/models/discord/role.py
107
108
109
110
@property
def guild(self) -> "Guild":
    """The guild object this role is from."""
    return self._client.cache.get_guild(self._guild_id)  # pyright: ignore [reportGeneralTypeIssues]

default() property

Is this the @everyone role.

Source code in naff/models/discord/role.py
112
113
114
115
@property
def default(self) -> bool:
    """Is this the `@everyone` role."""
    return self.id == self._guild_id

bot_managed() property

Is this role owned/managed by a bot.

Source code in naff/models/discord/role.py
117
118
119
120
@property
def bot_managed(self) -> bool:
    """Is this role owned/managed by a bot."""
    return self._bot_id is not None

mention() property

Returns a string that would mention the role.

Source code in naff/models/discord/role.py
122
123
124
125
@property
def mention(self) -> str:
    """Returns a string that would mention the role."""
    return f"<@&{self.id}>" if self.id != self._guild_id else "@everyone"

integration() property

Is this role owned/managed by an integration.

Source code in naff/models/discord/role.py
127
128
129
130
@property
def integration(self) -> bool:
    """Is this role owned/managed by an integration."""
    return self._integration_id is not None

members() property

List of members with this role

Source code in naff/models/discord/role.py
132
133
134
135
@property
def members(self) -> list["Member"]:
    """List of members with this role"""
    return [member for member in self.guild.members if member.has_role(self)]

icon() property

The icon of this role

Note

You have to use this method instead of the _icon attribute, because the first does account for unicode emojis

Source code in naff/models/discord/role.py
137
138
139
140
141
142
143
144
145
@property
def icon(self) -> Asset | PartialEmoji | None:
    """
    The icon of this role

    !!! note
        You have to use this method instead of the `_icon` attribute, because the first does account for unicode emojis
    """
    return self._icon or self._unicode_emoji

is_assignable() property

Can this role be assigned or removed by this bot?

Note

This does not account for permissions, only the role hierarchy

Source code in naff/models/discord/role.py
147
148
149
150
151
152
153
154
155
156
@property
def is_assignable(self) -> bool:
    """
    Can this role be assigned or removed by this bot?

    !!! note
        This does not account for permissions, only the role hierarchy

    """
    return (self.default or self.guild.me.top_role > self) and not self.managed

delete(reason=MISSING) async

Delete this role.

Parameters:

Name Type Description Default
reason str | Missing

An optional reason for this deletion

MISSING
Source code in naff/models/discord/role.py
158
159
160
161
162
163
164
165
166
async def delete(self, reason: str | Missing = MISSING) -> None:
    """
    Delete this role.

    Args:
        reason: An optional reason for this deletion

    """
    await self._client.http.delete_guild_role(self._guild_id, self.id, reason)

edit(name=None, permissions=None, color=None, hoist=None, mentionable=None) async

Edit this role, all arguments are optional.

Parameters:

Name Type Description Default
name str | None

name of the role

None
permissions str | None

New permissions to use

None
color Color | COLOR_TYPES | None

The color of the role

None
hoist bool | None

whether the role should be displayed separately in the sidebar

None
mentionable bool | None

whether the role should be mentionable

None

Returns:

Type Description
Role

Role with updated information

Source code in naff/models/discord/role.py
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
async def edit(
    self,
    name: str | None = None,
    permissions: str | None = None,
    color: Color | COLOR_TYPES | None = None,
    hoist: bool | None = None,
    mentionable: bool | None = None,
) -> "Role":
    """
    Edit this role, all arguments are optional.

    Args:
        name: name of the role
        permissions: New permissions to use
        color: The color of the role
        hoist: whether the role should be displayed separately in the sidebar
        mentionable: whether the role should be mentionable

    Returns:
        Role with updated information

    """
    color = process_color(color)

    payload = dict_filter(
        {"name": name, "permissions": permissions, "color": color, "hoist": hoist, "mentionable": mentionable}
    )

    r_data = await self._client.http.modify_guild_role(self._guild_id, self.id, payload)
    r_data = dict(r_data)  # to convert typed dict to regular dict
    r_data["guild_id"] = self._guild_id
    return self.from_dict(r_data, self._client)

PartialEmoji

Bases: SnowflakeObject, DictSerializationMixin

Represent a basic ("partial") emoji used in discord.

Source code in naff/models/discord/emoji.py
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
@define(kw_only=False)
class PartialEmoji(SnowflakeObject, DictSerializationMixin):
    """Represent a basic ("partial") emoji used in discord."""

    id: Optional["Snowflake_Type"] = field(
        repr=True, default=None, converter=optional(to_snowflake)
    )  # can be None for Standard Emoji
    """The custom emoji id. Leave empty if you are using standard unicode emoji."""
    name: Optional[str] = field(repr=True, default=None)
    """The custom emoji name, or standard unicode emoji in string"""
    animated: bool = field(repr=True, default=False)
    """Whether this emoji is animated"""

    @classmethod
    def from_str(cls, emoji_str: str) -> "PartialEmoji":
        """
        Generate a PartialEmoji from a discord Emoji string representation, or unicode emoji.

        Handles:
            <:emoji_name:emoji_id>
            :emoji_name:emoji_id
            <a:emoji_name:emoji_id>
            a:emoji_name:emoji_id
            👋

        Args:
            emoji_str: The string representation an emoji

        Returns:
            A PartialEmoji object

        Raises:
            ValueError: if the string cannot be parsed

        """
        parsed = emoji_regex.findall(emoji_str)
        if parsed:
            parsed = tuple(filter(None, parsed[0]))
            if len(parsed) == 3:
                return cls(name=parsed[1], id=parsed[2], animated=True)
            else:
                return cls(name=parsed[0], id=parsed[1])
        else:
            return cls(name=emoji_str)

    def __str__(self) -> str:
        s = self.req_format
        if self.id:
            s = f"<{'a:' if self.animated else ':'}{s}>"
        return s

    def __eq__(self, other) -> bool:
        if not isinstance(other, PartialEmoji):
            return False
        if self.id:
            return self.id == other.id
        return self.name == other.name

    @property
    def req_format(self) -> str:
        """Format used for web request."""
        if self.id:
            return f"{self.name}:{self.id}"
        else:
            return self.name

id: Optional[Snowflake_Type] = field(repr=True, default=None, converter=optional(to_snowflake)) class-attribute

The custom emoji id. Leave empty if you are using standard unicode emoji.

name: Optional[str] = field(repr=True, default=None) class-attribute

The custom emoji name, or standard unicode emoji in string

animated: bool = field(repr=True, default=False) class-attribute

Whether this emoji is animated

from_str(emoji_str) classmethod

Generate a PartialEmoji from a discord Emoji string representation, or unicode emoji.

Handles

<:emoji_name:emoji_id> :emoji_name:emoji_id a:emoji_name:emoji_id 👋

Parameters:

Name Type Description Default
emoji_str str

The string representation an emoji

required

Returns:

Type Description
PartialEmoji

A PartialEmoji object

Raises:

Type Description
ValueError

if the string cannot be parsed

Source code in naff/models/discord/emoji.py
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
@classmethod
def from_str(cls, emoji_str: str) -> "PartialEmoji":
    """
    Generate a PartialEmoji from a discord Emoji string representation, or unicode emoji.

    Handles:
        <:emoji_name:emoji_id>
        :emoji_name:emoji_id
        <a:emoji_name:emoji_id>
        a:emoji_name:emoji_id
        👋

    Args:
        emoji_str: The string representation an emoji

    Returns:
        A PartialEmoji object

    Raises:
        ValueError: if the string cannot be parsed

    """
    parsed = emoji_regex.findall(emoji_str)
    if parsed:
        parsed = tuple(filter(None, parsed[0]))
        if len(parsed) == 3:
            return cls(name=parsed[1], id=parsed[2], animated=True)
        else:
            return cls(name=parsed[0], id=parsed[1])
    else:
        return cls(name=emoji_str)

req_format() property

Format used for web request.

Source code in naff/models/discord/emoji.py
82
83
84
85
86
87
88
@property
def req_format(self) -> str:
    """Format used for web request."""
    if self.id:
        return f"{self.name}:{self.id}"
    else:
        return self.name

CustomEmoji

Bases: PartialEmoji, ClientObject

Represent a custom emoji in a guild with all its properties.

Source code in naff/models/discord/emoji.py
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
@define()
class CustomEmoji(PartialEmoji, ClientObject):
    """Represent a custom emoji in a guild with all its properties."""

    _client: "Client" = field(metadata=no_export_meta)

    require_colons: bool = field(default=False)
    """Whether this emoji must be wrapped in colons"""
    managed: bool = field(default=False)
    """Whether this emoji is managed"""
    available: bool = field(default=False)
    """Whether this emoji can be used, may be false due to loss of Server Boosts."""

    _creator_id: Optional["Snowflake_Type"] = field(default=None, converter=optional(to_snowflake))
    _role_ids: List["Snowflake_Type"] = field(factory=list, converter=optional(list_converter(to_snowflake)))
    _guild_id: "Snowflake_Type" = field(default=None, converter=to_snowflake)

    @classmethod
    def _process_dict(cls, data: Dict[str, Any], client: "Client") -> Dict[str, Any]:
        creator_dict = data.pop("user", None)
        data["creator_id"] = client.cache.place_user_data(creator_dict).id if creator_dict else None

        if "roles" in data:
            data["role_ids"] = data.pop("roles")

        return data

    @classmethod
    def from_dict(cls, data: Dict[str, Any], client: "Client", guild_id: int) -> "CustomEmoji":
        data = cls._process_dict(data, client)
        return cls(client=client, guild_id=guild_id, **cls._filter_kwargs(data, cls._get_init_keys()))

    @property
    def guild(self) -> "Guild":
        """The guild this emoji belongs to."""
        return self._client.cache.get_guild(self._guild_id)

    @property
    def creator(self) -> Optional[Union["Member", "User"]]:
        """The member that created this emoji."""
        return self._client.cache.get_member(self._creator_id, self._guild_id) or self._client.cache.get_user(
            self._creator_id
        )

    @property
    def roles(self) -> List["Role"]:
        """The roles allowed to use this emoji."""
        return [self._client.cache.get_role(role_id) for role_id in self._role_ids]

    @property
    def is_usable(self) -> bool:
        """Determines if this emoji is usable by the current user."""
        if not self.available:
            return False

        guild = self.guild
        return any(e_role_id in guild.me._role_ids for e_role_id in self._role_ids)

    async def edit(
        self,
        name: Optional[str] = None,
        roles: Optional[List[Union["Snowflake_Type", "Role"]]] = None,
        reason: Optional[str] = None,
    ) -> "CustomEmoji":
        """
        Modify the custom emoji information.

        Args:
            name: The name of the emoji.
            roles: The roles allowed to use this emoji.
            reason: Attach a reason to this action, used for audit logs.

        Returns:
            The newly modified custom emoji.

        """
        data_payload = dict_filter_none(
            {
                "name": name,
                "roles": roles,
            }
        )

        updated_data = await self._client.http.modify_guild_emoji(data_payload, self._guild_id, self.id, reason=reason)
        self.update_from_dict(updated_data)
        return self

    async def delete(self, reason: Optional[str] = None) -> None:
        """
        Deletes the custom emoji from the guild.

        Args:
            reason: Attach a reason to this action, used for audit logs.

        """
        if not self._guild_id:
            raise ValueError("Cannot delete emoji, no guild id set.")

        await self._client.http.delete_guild_emoji(self._guild_id, self.id, reason=reason)

require_colons: bool = field(default=False) class-attribute

Whether this emoji must be wrapped in colons

managed: bool = field(default=False) class-attribute

Whether this emoji is managed

available: bool = field(default=False) class-attribute

Whether this emoji can be used, may be false due to loss of Server Boosts.

guild() property

The guild this emoji belongs to.

Source code in naff/models/discord/emoji.py
123
124
125
126
@property
def guild(self) -> "Guild":
    """The guild this emoji belongs to."""
    return self._client.cache.get_guild(self._guild_id)

creator() property

The member that created this emoji.

Source code in naff/models/discord/emoji.py
128
129
130
131
132
133
@property
def creator(self) -> Optional[Union["Member", "User"]]:
    """The member that created this emoji."""
    return self._client.cache.get_member(self._creator_id, self._guild_id) or self._client.cache.get_user(
        self._creator_id
    )

roles() property

The roles allowed to use this emoji.

Source code in naff/models/discord/emoji.py
135
136
137
138
@property
def roles(self) -> List["Role"]:
    """The roles allowed to use this emoji."""
    return [self._client.cache.get_role(role_id) for role_id in self._role_ids]

is_usable() property

Determines if this emoji is usable by the current user.

Source code in naff/models/discord/emoji.py
140
141
142
143
144
145
146
147
@property
def is_usable(self) -> bool:
    """Determines if this emoji is usable by the current user."""
    if not self.available:
        return False

    guild = self.guild
    return any(e_role_id in guild.me._role_ids for e_role_id in self._role_ids)

edit(name=None, roles=None, reason=None) async

Modify the custom emoji information.

Parameters:

Name Type Description Default
name Optional[str]

The name of the emoji.

None
roles Optional[List[Union[Snowflake_Type, Role]]]

The roles allowed to use this emoji.

None
reason Optional[str]

Attach a reason to this action, used for audit logs.

None

Returns:

Type Description
CustomEmoji

The newly modified custom emoji.

Source code in naff/models/discord/emoji.py
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
async def edit(
    self,
    name: Optional[str] = None,
    roles: Optional[List[Union["Snowflake_Type", "Role"]]] = None,
    reason: Optional[str] = None,
) -> "CustomEmoji":
    """
    Modify the custom emoji information.

    Args:
        name: The name of the emoji.
        roles: The roles allowed to use this emoji.
        reason: Attach a reason to this action, used for audit logs.

    Returns:
        The newly modified custom emoji.

    """
    data_payload = dict_filter_none(
        {
            "name": name,
            "roles": roles,
        }
    )

    updated_data = await self._client.http.modify_guild_emoji(data_payload, self._guild_id, self.id, reason=reason)
    self.update_from_dict(updated_data)
    return self

delete(reason=None) async

Deletes the custom emoji from the guild.

Parameters:

Name Type Description Default
reason Optional[str]

Attach a reason to this action, used for audit logs.

None
Source code in naff/models/discord/emoji.py
178
179
180
181
182
183
184
185
186
187
188
189
async def delete(self, reason: Optional[str] = None) -> None:
    """
    Deletes the custom emoji from the guild.

    Args:
        reason: Attach a reason to this action, used for audit logs.

    """
    if not self._guild_id:
        raise ValueError("Cannot delete emoji, no guild id set.")

    await self._client.http.delete_guild_emoji(self._guild_id, self.id, reason=reason)

process_emoji_req_format(emoji)

Processes the emoji parameter into the str format required by the API.

Parameters:

Name Type Description Default
emoji Optional[Union[PartialEmoji, dict, str]]

The emoji to process.

required

Returns:

Type Description
Optional[str]

formatted string for discord

Source code in naff/models/discord/emoji.py
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
def process_emoji_req_format(emoji: Optional[Union[PartialEmoji, dict, str]]) -> Optional[str]:
    """
    Processes the emoji parameter into the str format required by the API.

    Args:
        emoji: The emoji to process.

    Returns:
        formatted string for discord

    """
    if not emoji:
        return emoji

    if isinstance(emoji, str):
        emoji = PartialEmoji.from_str(emoji)

    if isinstance(emoji, dict):
        emoji = PartialEmoji.from_dict(emoji)

    if isinstance(emoji, PartialEmoji):
        return emoji.req_format

    raise ValueError(f"Invalid emoji: {emoji}")

process_emoji(emoji)

Processes the emoji parameter into the dictionary format required by the API.

Parameters:

Name Type Description Default
emoji Optional[Union[PartialEmoji, dict, str]]

The emoji to process.

required

Returns:

Type Description
Optional[dict]

formatted dictionary for discord

Source code in naff/models/discord/emoji.py
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
def process_emoji(emoji: Optional[Union[PartialEmoji, dict, str]]) -> Optional[dict]:
    """
    Processes the emoji parameter into the dictionary format required by the API.

    Args:
        emoji: The emoji to process.

    Returns:
        formatted dictionary for discord

    """
    if not emoji:
        return emoji

    if isinstance(emoji, dict):
        return emoji

    if isinstance(emoji, str):
        emoji = PartialEmoji.from_str(emoji)

    if isinstance(emoji, PartialEmoji):
        return emoji.to_dict()

    raise ValueError(f"Invalid emoji: {emoji}")

Message

Attachment

Bases: DiscordObject

Source code in naff/models/discord/message.py
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
@define()
class Attachment(DiscordObject):
    filename: str = field()
    """name of file attached"""
    description: Optional[str] = field(default=None)
    """description for the file"""
    content_type: Optional[str] = field(default=None)
    """the attachment's media type"""
    size: int = field()
    """size of file in bytes"""
    url: str = field()
    """source url of file"""
    proxy_url: str = field()
    """a proxied url of file"""
    height: Optional[int] = field(default=None)
    """height of file (if image)"""
    width: Optional[int] = field(default=None)
    """width of file (if image)"""
    ephemeral: bool = field(default=False)
    """whether this attachment is ephemeral"""

    @property
    def resolution(self) -> tuple[Optional[int], Optional[int]]:
        """Returns the image resolution of the attachment file"""
        return self.height, self.width

filename: str = field() class-attribute

name of file attached

description: Optional[str] = field(default=None) class-attribute

description for the file

content_type: Optional[str] = field(default=None) class-attribute

the attachment's media type

size: int = field() class-attribute

size of file in bytes

url: str = field() class-attribute

source url of file

proxy_url: str = field() class-attribute

a proxied url of file

height: Optional[int] = field(default=None) class-attribute

height of file (if image)

width: Optional[int] = field(default=None) class-attribute

width of file (if image)

ephemeral: bool = field(default=False) class-attribute

whether this attachment is ephemeral

resolution() property

Returns the image resolution of the attachment file

Source code in naff/models/discord/message.py
70
71
72
73
@property
def resolution(self) -> tuple[Optional[int], Optional[int]]:
    """Returns the image resolution of the attachment file"""
    return self.height, self.width

ChannelMention

Bases: DiscordObject

Source code in naff/models/discord/message.py
76
77
78
79
80
81
82
83
@define()
class ChannelMention(DiscordObject):
    guild_id: "Snowflake_Type" = field()
    """id of the guild containing the channel"""
    type: ChannelTypes = field(converter=ChannelTypes)
    """the type of channel"""
    name: str = field()
    """the name of the channel"""

guild_id: Snowflake_Type = field() class-attribute

id of the guild containing the channel

type: ChannelTypes = field(converter=ChannelTypes) class-attribute

the type of channel

name: str = field() class-attribute

the name of the channel

MessageActivity dataclass

Source code in naff/models/discord/message.py
86
87
88
89
90
91
@dataclass
class MessageActivity:
    type: MessageActivityTypes
    """type of message activity"""
    party_id: str = None
    """party_id from a Rich Presence event"""

type: MessageActivityTypes class-attribute

type of message activity

party_id: str = None class-attribute

party_id from a Rich Presence event

MessageReference

Bases: DictSerializationMixin

Reference to an originating message.

Can be used for replies.

Source code in naff/models/discord/message.py
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
@define()
class MessageReference(DictSerializationMixin):
    """
    Reference to an originating message.

    Can be used for replies.

    """

    message_id: int = field(default=None, converter=optional_c(to_snowflake))
    """id of the originating message."""
    channel_id: Optional[int] = field(default=None, converter=optional_c(to_snowflake))
    """id of the originating message's channel."""
    guild_id: Optional[int] = field(default=None, converter=optional_c(to_snowflake))
    """id of the originating message's guild."""
    fail_if_not_exists: bool = field(default=True)
    """When sending a message, whether to error if the referenced message doesn't exist instead of sending as a normal (non-reply) message, default true."""

    @classmethod
    def for_message(cls, message: "Message", fail_if_not_exists: bool = True) -> "MessageReference":
        """
        Creates a reference to a message.

        parameters
            message: The target message to reference.
            fail_if_not_exists: Whether to error if the referenced message doesn't exist instead of sending as a normal (non-reply) message

        Returns:
            A MessageReference object.

        """
        return cls(
            message_id=message.id,
            channel_id=message._channel_id,
            guild_id=message._guild_id,
            fail_if_not_exists=fail_if_not_exists,
        )

message_id: int = field(default=None, converter=optional_c(to_snowflake)) class-attribute

id of the originating message.

channel_id: Optional[int] = field(default=None, converter=optional_c(to_snowflake)) class-attribute

id of the originating message's channel.

guild_id: Optional[int] = field(default=None, converter=optional_c(to_snowflake)) class-attribute

id of the originating message's guild.

fail_if_not_exists: bool = field(default=True) class-attribute

When sending a message, whether to error if the referenced message doesn't exist instead of sending as a normal (non-reply) message, default true.

for_message(message, fail_if_not_exists=True) classmethod

Creates a reference to a message.

parameters message: The target message to reference. fail_if_not_exists: Whether to error if the referenced message doesn't exist instead of sending as a normal (non-reply) message

Returns:

Type Description
MessageReference

A MessageReference object.

Source code in naff/models/discord/message.py
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
@classmethod
def for_message(cls, message: "Message", fail_if_not_exists: bool = True) -> "MessageReference":
    """
    Creates a reference to a message.

    parameters
        message: The target message to reference.
        fail_if_not_exists: Whether to error if the referenced message doesn't exist instead of sending as a normal (non-reply) message

    Returns:
        A MessageReference object.

    """
    return cls(
        message_id=message.id,
        channel_id=message._channel_id,
        guild_id=message._guild_id,
        fail_if_not_exists=fail_if_not_exists,
    )

MessageInteraction

Bases: DiscordObject

Source code in naff/models/discord/message.py
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
@define()
class MessageInteraction(DiscordObject):
    type: InteractionTypes = field(converter=InteractionTypes)
    """the type of interaction"""
    name: str = field()
    """the name of the application command"""

    _user_id: "Snowflake_Type" = field()

    @classmethod
    def _process_dict(cls, data: Dict[str, Any], client: "Client") -> Dict[str, Any]:
        user_data = data["user"]
        data["user_id"] = client.cache.place_user_data(user_data).id
        return data

    async def user(self) -> "models.User":
        """Get the user associated with this interaction."""
        return await self.get_user(self._user_id)

type: InteractionTypes = field(converter=InteractionTypes) class-attribute

the type of interaction

name: str = field() class-attribute

the name of the application command

user() async

Get the user associated with this interaction.

Source code in naff/models/discord/message.py
148
149
150
async def user(self) -> "models.User":
    """Get the user associated with this interaction."""
    return await self.get_user(self._user_id)

AllowedMentions

Bases: DictSerializationMixin

The allowed mention field allows for more granular control over mentions without various hacks to the message content.

This will always validate against message content to avoid phantom pings, and check against user/bot permissions.

Source code in naff/models/discord/message.py
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
@define(kw_only=False)
class AllowedMentions(DictSerializationMixin):
    """
    The allowed mention field allows for more granular control over mentions without various hacks to the message content.

    This will always validate against message content to avoid phantom
    pings, and check against user/bot permissions.

    """

    parse: Optional[List[str]] = field(factory=list)
    """An array of allowed mention types to parse from the content."""
    roles: Optional[List["Snowflake_Type"]] = field(factory=list, converter=to_snowflake_list)
    """Array of role_ids to mention. (Max size of 100)"""
    users: Optional[List["Snowflake_Type"]] = field(factory=list, converter=to_snowflake_list)
    """Array of user_ids to mention. (Max size of 100)"""
    replied_user = field(default=False)
    """For replies, whether to mention the author of the message being replied to. (default false)"""

    def add_parse(self, *mention_types: Union["MentionTypes", str]) -> None:
        """
        Add a mention type to the list of allowed mentions to parse.

        Args:
            *mention_types: The types of mentions to add

        """
        for mention_type in mention_types:
            if not isinstance(mention_type, MentionTypes) and mention_type not in MentionTypes.__members__.values():
                raise ValueError(f"Invalid mention type: {mention_type}")
            self.parse.append(mention_type)

    def add_roles(self, *roles: Union["models.Role", "Snowflake_Type"]) -> None:
        """
        Add roles that are allowed to be mentioned.

        Args:
            *roles: The roles to add

        """
        for role in roles:
            self.roles.append(to_snowflake(role))

    def add_users(self, *users: Union["models.Member", "models.BaseUser", "Snowflake_Type"]) -> None:
        """
        Add users that are allowed to be mentioned.

        Args:
            *users: The users to add

        """
        for user in users:
            self.users.append(to_snowflake(user))

    @classmethod
    def all(cls) -> "AllowedMentions":
        """
        Allows every user and role to be mentioned.

        Returns:
            An AllowedMentions object

        """
        return cls(parse=list(MentionTypes.__members__.values()), replied_user=True)

    @classmethod
    def none(cls) -> "AllowedMentions":
        """
        Disallows any user or role to be mentioned.

        Returns:
            An AllowedMentions object

        """
        return cls()

parse: Optional[List[str]] = field(factory=list) class-attribute

An array of allowed mention types to parse from the content.

roles: Optional[List[Snowflake_Type]] = field(factory=list, converter=to_snowflake_list) class-attribute

Array of role_ids to mention. (Max size of 100)

users: Optional[List[Snowflake_Type]] = field(factory=list, converter=to_snowflake_list) class-attribute

Array of user_ids to mention. (Max size of 100)

replied_user = field(default=False) class-attribute

For replies, whether to mention the author of the message being replied to. (default false)

add_parse(*mention_types)

Add a mention type to the list of allowed mentions to parse.

Parameters:

Name Type Description Default
*mention_types Union[MentionTypes, str]

The types of mentions to add

()
Source code in naff/models/discord/message.py
172
173
174
175
176
177
178
179
180
181
182
183
def add_parse(self, *mention_types: Union["MentionTypes", str]) -> None:
    """
    Add a mention type to the list of allowed mentions to parse.

    Args:
        *mention_types: The types of mentions to add

    """
    for mention_type in mention_types:
        if not isinstance(mention_type, MentionTypes) and mention_type not in MentionTypes.__members__.values():
            raise ValueError(f"Invalid mention type: {mention_type}")
        self.parse.append(mention_type)

add_roles(*roles)

Add roles that are allowed to be mentioned.

Parameters:

Name Type Description Default
*roles Union[Role, Snowflake_Type]

The roles to add

()
Source code in naff/models/discord/message.py
185
186
187
188
189
190
191
192
193
194
def add_roles(self, *roles: Union["models.Role", "Snowflake_Type"]) -> None:
    """
    Add roles that are allowed to be mentioned.

    Args:
        *roles: The roles to add

    """
    for role in roles:
        self.roles.append(to_snowflake(role))

add_users(*users)

Add users that are allowed to be mentioned.

Parameters:

Name Type Description Default
*users Union[Member, BaseUser, Snowflake_Type]

The users to add

()
Source code in naff/models/discord/message.py
196
197
198
199
200
201
202
203
204
205
def add_users(self, *users: Union["models.Member", "models.BaseUser", "Snowflake_Type"]) -> None:
    """
    Add users that are allowed to be mentioned.

    Args:
        *users: The users to add

    """
    for user in users:
        self.users.append(to_snowflake(user))

all() classmethod

Allows every user and role to be mentioned.

Returns:

Type Description
AllowedMentions

An AllowedMentions object

Source code in naff/models/discord/message.py
207
208
209
210
211
212
213
214
215
216
@classmethod
def all(cls) -> "AllowedMentions":
    """
    Allows every user and role to be mentioned.

    Returns:
        An AllowedMentions object

    """
    return cls(parse=list(MentionTypes.__members__.values()), replied_user=True)

none() classmethod

Disallows any user or role to be mentioned.

Returns:

Type Description
AllowedMentions

An AllowedMentions object

Source code in naff/models/discord/message.py
218
219
220
221
222
223
224
225
226
227
@classmethod
def none(cls) -> "AllowedMentions":
    """
    Disallows any user or role to be mentioned.

    Returns:
        An AllowedMentions object

    """
    return cls()

BaseMessage

Bases: DiscordObject

Source code in naff/models/discord/message.py
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
@define()
class BaseMessage(DiscordObject):
    _channel_id: "Snowflake_Type" = field(default=MISSING, converter=to_optional_snowflake)
    _thread_channel_id: Optional["Snowflake_Type"] = field(default=None, converter=to_optional_snowflake)
    _guild_id: Optional["Snowflake_Type"] = field(default=None, converter=to_optional_snowflake)
    _author_id: "Snowflake_Type" = field(default=MISSING, converter=to_optional_snowflake)

    @property
    def guild(self) -> "models.Guild":
        """The guild the message was sent in"""
        return self._client.cache.get_guild(self._guild_id)

    @property
    def channel(self) -> "models.TYPE_MESSAGEABLE_CHANNEL":
        """The channel the message was sent in"""
        channel = self._client.cache.get_channel(self._channel_id)

        if not self._guild_id and not channel:
            # allow dm operations without fetching a dm channel from API
            channel = BaseChannel.from_dict_factory({"id": self._channel_id, "type": ChannelTypes.DM}, self._client)
            if self.author:
                channel.recipients = [self.author]
        return channel

    @property
    def thread(self) -> "models.TYPE_THREAD_CHANNEL":
        """The thread that was started from this message, includes thread member object"""
        return self._client.cache.get_channel(self._thread_channel_id)

    @property
    def author(self) -> Union["models.Member", "models.User"]:
        """The author of this message. Only a valid user in the case where the message is generated by a user or bot user."""
        if self._author_id:
            member = None
            if self._guild_id:
                member = self._client.cache.get_member(self._guild_id, self._author_id)
            return member or self._client.cache.get_user(self._author_id)
        return MISSING

guild() property

The guild the message was sent in

Source code in naff/models/discord/message.py
237
238
239
240
@property
def guild(self) -> "models.Guild":
    """The guild the message was sent in"""
    return self._client.cache.get_guild(self._guild_id)

channel() property

The channel the message was sent in

Source code in naff/models/discord/message.py
242
243
244
245
246
247
248
249
250
251
252
@property
def channel(self) -> "models.TYPE_MESSAGEABLE_CHANNEL":
    """The channel the message was sent in"""
    channel = self._client.cache.get_channel(self._channel_id)

    if not self._guild_id and not channel:
        # allow dm operations without fetching a dm channel from API
        channel = BaseChannel.from_dict_factory({"id": self._channel_id, "type": ChannelTypes.DM}, self._client)
        if self.author:
            channel.recipients = [self.author]
    return channel

thread() property

The thread that was started from this message, includes thread member object

Source code in naff/models/discord/message.py
254
255
256
257
@property
def thread(self) -> "models.TYPE_THREAD_CHANNEL":
    """The thread that was started from this message, includes thread member object"""
    return self._client.cache.get_channel(self._thread_channel_id)

author() property

The author of this message. Only a valid user in the case where the message is generated by a user or bot user.

Source code in naff/models/discord/message.py
259
260
261
262
263
264
265
266
267
@property
def author(self) -> Union["models.Member", "models.User"]:
    """The author of this message. Only a valid user in the case where the message is generated by a user or bot user."""
    if self._author_id:
        member = None
        if self._guild_id:
            member = self._client.cache.get_member(self._guild_id, self._author_id)
        return member or self._client.cache.get_user(self._author_id)
    return MISSING

Message

Bases: BaseMessage

Source code in naff/models/discord/message.py
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
@define()
class Message(BaseMessage):
    content: str = field(default=MISSING)
    """Contents of the message"""
    timestamp: "models.Timestamp" = field(default=MISSING, converter=optional_c(timestamp_converter))
    """When this message was sent"""
    edited_timestamp: Optional["models.Timestamp"] = field(default=None, converter=optional_c(timestamp_converter))
    """When this message was edited (or `None` if never)"""
    tts: bool = field(default=False)
    """Whether this was a TTS message"""
    mention_everyone: bool = field(default=False)
    """Whether this message mentions everyone"""
    mention_channels: List[ChannelMention] = field(factory=list)
    """Channels specifically mentioned in this message"""
    attachments: List[Attachment] = field(factory=list)
    """Any attached files"""
    embeds: List["models.Embed"] = field(factory=list)
    """Any embedded content"""
    reactions: List["models.Reaction"] = field(factory=list)
    """Reactions to the message"""
    nonce: Optional[Union[int, str]] = field(default=None)
    """Used for validating a message was sent"""
    pinned: bool = field(default=False)
    """Whether this message is pinned"""
    webhook_id: Optional["Snowflake_Type"] = field(default=None, converter=to_optional_snowflake)
    """If the message is generated by a webhook, this is the webhook's id"""
    type: MessageTypes = field(default=MISSING, converter=optional_c(MessageTypes))
    """Type of message"""
    activity: Optional[MessageActivity] = field(default=None, converter=optional_c(MessageActivity))
    """Activity sent with Rich Presence-related chat embeds"""
    application: Optional["models.Application"] = field(default=None)  # TODO: partial application
    """Application sent with Rich Presence-related chat embeds"""
    application_id: Optional["Snowflake_Type"] = field(default=None, converter=to_optional_snowflake)
    """If the message is an Interaction or application-owned webhook, this is the id of the application"""
    message_reference: Optional[MessageReference] = field(
        default=None, converter=optional_c(MessageReference.from_dict)
    )
    """Data showing the source of a crosspost, channel follow add, pin, or reply message"""
    flags: MessageFlags = field(default=MessageFlags.NONE, converter=MessageFlags)
    """Message flags combined as a bitfield"""
    interaction: Optional["MessageInteraction"] = field(default=None)
    """Sent if the message is a response to an Interaction"""
    components: Optional[List["models.ActionRow"]] = field(default=None)
    """Sent if the message contains components like buttons, action rows, or other interactive components"""
    sticker_items: Optional[List["models.StickerItem"]] = field(default=None)
    """Sent if the message contains stickers"""
    _mention_ids: List["Snowflake_Type"] = field(factory=list)
    _mention_roles: List["Snowflake_Type"] = field(factory=list)
    _referenced_message_id: Optional["Snowflake_Type"] = field(default=None)

    @property
    async def mention_users(self) -> AsyncGenerator["models.Member", None]:
        """A generator of users mentioned in this message"""
        for u_id in self._mention_ids:
            yield await self._client.cache.fetch_member(self._guild_id, u_id)

    @property
    async def mention_roles(self) -> AsyncGenerator["models.Role", None]:
        """A generator of roles mentioned in this message"""
        for r_id in self._mention_roles:
            yield await self._client.cache.fetch_role(self._guild_id, r_id)

    @property
    def thread(self) -> "models.TYPE_THREAD_CHANNEL":
        """The thread that was started from this message, if any"""
        return self._client.cache.get_channel(self.id)

    async def fetch_referenced_message(self) -> Optional["Message"]:
        """
        Fetch the message this message is referencing, if any.

        Returns:
            The referenced message, if found

        """
        if self._referenced_message_id is None:
            return None
        return await self._client.cache.fetch_message(self._channel_id, self._referenced_message_id)

    def get_referenced_message(self) -> Optional["Message"]:
        """
        Get the message this message is referencing, if any.

        Returns:
            The referenced message, if found

        """
        if self._referenced_message_id is None:
            return None
        return self._client.cache.get_message(self._channel_id, self._referenced_message_id)

    @classmethod
    def _process_dict(cls, data: dict, client: "Client") -> dict:
        if author_data := data.pop("author", None):
            if "guild_id" in data and "member" in data:
                author_data["member"] = data.pop("member")
                data["author_id"] = client.cache.place_member_data(data["guild_id"], author_data).id
            else:
                data["author_id"] = client.cache.place_user_data(author_data).id

        if mentions_data := data.pop("mentions", None):
            mention_ids = []
            for user_data in mentions_data:
                if "guild_id" in data and "member" in user_data:
                    mention_ids.append(client.cache.place_member_data(data["guild_id"], user_data).id)
                else:
                    mention_ids.append(client.cache.place_user_data(user_data).id)
            data["mention_ids"] = mention_ids

        found_ids = []
        mention_channels = []
        if "mention_channels" in data:
            for channel_data in data["mention_channels"]:
                mention_channels.append(ChannelMention.from_dict(channel_data, client))
                found_ids.append(channel_data["id"])
        if "content" in data:
            for channel_id in channel_mention.findall(data["content"]):
                if channel_id not in found_ids and (channel := client.get_channel(channel_id)):
                    channel_data = {
                        "id": channel.id,
                        "guild_id": channel._guild_id,
                        "type": channel.type,
                        "name": channel.name,
                    }
                    mention_channels.append(ChannelMention.from_dict(channel_data, client))
        if len(mention_channels) > 0:
            data["mention_channels"] = mention_channels

        if "attachments" in data:
            data["attachments"] = Attachment.from_list(data.get("attachments"), client)

        if "embeds" in data:
            data["embeds"] = models.Embed.from_list(data.get("embeds"))

        if "reactions" in data:
            reactions = []
            for reaction_data in data["reactions"]:
                reactions.append(
                    models.Reaction.from_dict(
                        reaction_data | {"message_id": data["id"], "channel_id": data["channel_id"]}, client
                    )
                )
            data["reactions"] = reactions

        # TODO: Convert to application object

        if ref_message_data := data.pop("referenced_message", None):
            if not ref_message_data.get("guild_id"):
                ref_message_data["guild_id"] = data.get("guild_id")
            _m = client.cache.place_message_data(ref_message_data)
            data["referenced_message_id"] = _m.id

        if "interaction" in data:
            data["interaction"] = MessageInteraction.from_dict(data["interaction"], client)

        if thread_data := data.pop("thread", None):
            data["thread_channel_id"] = client.cache.place_channel_data(thread_data).id

        if "components" in data:
            components = []
            for component_data in data["components"]:
                components.append(models.BaseComponent.from_dict_factory(component_data))
            data["components"] = components

        if "sticker_items" in data:
            data["sticker_items"] = models.StickerItem.from_list(data["sticker_items"], client)

        return data

    @property
    def system_content(self) -> Optional[str]:
        """Content for system messages. (boosts, welcomes, etc)"""
        match self.type:
            case MessageTypes.USER_PREMIUM_GUILD_SUBSCRIPTION:
                return f"{self.author.mention} just boosted the server!"
            case MessageTypes.USER_PREMIUM_GUILD_SUBSCRIPTION_TIER_1:
                return f"{self.author.mention} just boosted the server! {self.guild.name} has achieved **Level 1!**"
            case MessageTypes.USER_PREMIUM_GUILD_SUBSCRIPTION_TIER_2:
                return f"{self.author.mention} just boosted the server! {self.guild.name} has achieved **Level 2!**"
            case MessageTypes.USER_PREMIUM_GUILD_SUBSCRIPTION_TIER_3:
                return f"{self.author.mention} just boosted the server! {self.guild.name} has achieved **Level 3!**"
            case MessageTypes.GUILD_MEMBER_JOIN:
                return GUILD_WELCOME_MESSAGES[
                    int(self.timestamp.timestamp() * 1000)
                    % len(GUILD_WELCOME_MESSAGES)
                    # This is how Discord calculates the welcome message.
                ].format(self.author.mention)
            case MessageTypes.THREAD_CREATED:
                return f"{self.author.mention} started a thread: {self.thread.mention}. See all **threads**."
            case MessageTypes.CHANNEL_FOLLOW_ADD:
                return f"{self.author.mention} has added **{self.content}** to this channel. Its most important updates will show up here."
            case MessageTypes.RECIPIENT_ADD:
                return f"{self.author.mention} added <@{self._mention_ids[0]}> to the thread."
            case MessageTypes.RECIPIENT_REMOVE:
                return f"{self.author.mention} removed <@{self._mention_ids[0]}> from the thread."
            case MessageTypes.CHANNEL_NAME_CHANGE:
                return f"{self.author.mention} changed the channel name: **{self.content}**."
            case MessageTypes.CHANNEL_PINNED_MESSAGE:
                return f"{self.author.mention} pinned a message. See all pinned messages"
            case MessageTypes.GUILD_DISCOVERY_DISQUALIFIED:
                return "This server has been removed from Server Discovery because it no longer passes all the requirements. Check Server Settings for more details."
            case MessageTypes.GUILD_DISCOVERY_REQUALIFIED:
                return "This server is eligible for Server Discovery again and has been automatically relisted!"
            case MessageTypes.GUILD_DISCOVERY_GRACE_PERIOD_INITIAL_WARNING:
                return "This server has failed Discovery activity requirements for 1 week. If this server fails for 4 weeks in a row, it will be automatically removed from Discovery."
            case MessageTypes.GUILD_DISCOVERY_GRACE_PERIOD_FINAL_WARNING:
                return "This server has failed Discovery activity requirements for 3 weeks in a row. If this server fails for 1 more week, it will be removed from Discovery."
            case MessageTypes.GUILD_INVITE_REMINDER:
                return "**Invite your friends**\nThe best way to setup a server is with your buddies!"
            case MessageTypes.THREAD_STARTER_MESSAGE:
                if referenced_message := self.get_referenced_message():
                    return referenced_message.content
                return "Sorry, we couldn't load the first message in this thread"
            case MessageTypes.AUTO_MODERATION_ACTION:
                keyword_matched_content = self.embeds[0].fields[4].value  # The words that triggered the action
                message_content = self.embeds[0].description.replace(
                    keyword_matched_content, f"**{keyword_matched_content}**"
                )
                rule = self.embeds[0].fields[0].value  # What rule was triggered
                channel = self.embeds[0].fields[1].value  # Channel that the action took place in
                return f'AutoMod has blocked a message in <#{channel}>. "{message_content}" from {self.author.mention}. Rule: {rule}.'
            case _:
                return None

    @property
    def jump_url(self) -> str:
        """A url that allows the client to *jump* to this message."""
        return f"https://discord.com/channels/{self._guild_id or '@me'}/{self._channel_id}/{self.id}"

    @property
    def proto_url(self) -> str:
        """A URL like `jump_url` that uses protocols."""
        return f"discord://-/channels/{self._guild_id or '@me'}/{self._channel_id}/{self.id}"

    async def edit(
        self,
        content: Optional[str] = None,
        embeds: Optional[Union[Sequence[Union["models.Embed", dict]], Union["models.Embed", dict]]] = None,
        embed: Optional[Union["models.Embed", dict]] = None,
        components: Optional[
            Union[
                Sequence[Sequence[Union["models.BaseComponent", dict]]],
                Sequence[Union["models.BaseComponent", dict]],
                "models.BaseComponent",
                dict,
            ]
        ] = None,
        allowed_mentions: Optional[Union[AllowedMentions, dict]] = None,
        attachments: Optional[Optional[Sequence[Union[Attachment, dict]]]] = None,
        files: Optional[Union[UPLOADABLE_TYPE, Sequence[UPLOADABLE_TYPE]]] = None,
        file: Optional[UPLOADABLE_TYPE] = None,
        tts: bool = False,
        flags: Optional[Union[int, MessageFlags]] = None,
    ) -> "Message":
        """
        Edits the message.

        Args:
            content: Message text content.
            embeds: Embedded rich content (up to 6000 characters).
            embed: Embedded rich content (up to 6000 characters).
            components: The components to include with the message.
            allowed_mentions: Allowed mentions for the message.
            attachments: The attachments to keep, only used when editing message.
            files: Files to send, the path, bytes or File() instance, defaults to None. You may have up to 10 files.
            file: Files to send, the path, bytes or File() instance, defaults to None. You may have up to 10 files.
            tts: Should this message use Text To Speech.
            flags: Message flags to apply.

        Returns:
            New message object with edits applied

        """
        message_payload = process_message_payload(
            content=content,
            embeds=embeds or embed,
            components=components,
            allowed_mentions=allowed_mentions,
            attachments=attachments,
            tts=tts,
            flags=flags,
        )

        if self.flags == MessageFlags.EPHEMERAL:
            raise EphemeralEditException

        message_data = await self._client.http.edit_message(message_payload, self._channel_id, self.id, files=files)
        if message_data:
            return self._client.cache.place_message_data(message_data)

    async def delete(self, delay: Absent[Optional[int]] = MISSING) -> None:
        """
        Delete message.

        Args:
            delay: Seconds to wait before deleting message.

        """
        if delay and delay > 0:

            async def delayed_delete() -> None:
                await asyncio.sleep(delay)
                try:
                    await self._client.http.delete_message(self._channel_id, self.id)
                except Exception:  # noqa: S110
                    pass  # No real way to handle this

            asyncio.create_task(delayed_delete())

        else:
            await self._client.http.delete_message(self._channel_id, self.id)

    async def reply(
        self,
        content: Optional[str] = None,
        embeds: Optional[Union[List[Union["models.Embed", dict]], Union["models.Embed", dict]]] = None,
        embed: Optional[Union["models.Embed", dict]] = None,
        **kwargs: Mapping[str, Any],
    ) -> "Message":
        """
        Reply to this message, takes all the same attributes as `send`.

        Args:
            content: Message text content.
            embeds: Embedded rich content (up to 6000 characters).
            embed: Embedded rich content (up to 6000 characters).
            **kwargs: Additional options to pass to `send`.

        Returns:
            New message object.

        """
        return await self.channel.send(content=content, reply_to=self, embeds=embeds or embed, **kwargs)

    async def create_thread(
        self,
        name: str,
        auto_archive_duration: Union[AutoArchiveDuration, int] = AutoArchiveDuration.ONE_DAY,
        reason: Optional[str] = None,
    ) -> "models.TYPE_THREAD_CHANNEL":
        """
        Create a thread from this message.

        Args:
            name: The name of this thread
            auto_archive_duration: duration in minutes to automatically archive the thread after recent activity, can be set to: 60, 1440, 4320, 10080
            reason: The optional reason for creating this thread

        Returns:
            The created thread object

        Raises:
            ThreadOutsideOfGuild: if this is invoked on a message outside of a guild

        """
        if self.channel.type not in (ChannelTypes.GUILD_TEXT, ChannelTypes.GUILD_NEWS):
            raise ThreadOutsideOfGuild

        thread_data = await self._client.http.create_thread(
            channel_id=self._channel_id,
            name=name,
            auto_archive_duration=auto_archive_duration,
            message_id=self.id,
            reason=reason,
        )
        return self._client.cache.place_channel_data(thread_data)

    async def suppress_embeds(self) -> "Message":
        """
        Suppress embeds for this message.

        !!! note
            Requires the `Permissions.MANAGE_MESSAGES` permission.

        """
        message_data = await self._client.http.edit_message(
            {"flags": MessageFlags.SUPPRESS_EMBEDS}, self._channel_id, self.id
        )
        if message_data:
            return self._client.cache.place_message_data(message_data)

    async def fetch_reaction(
        self,
        emoji: Union["models.PartialEmoji", dict, str],
        limit: Absent[int] = MISSING,
        after: Absent["Snowflake_Type"] = MISSING,
    ) -> List["models.User"]:
        """
        Fetches reactions of a specific emoji from this message.

        Args:
            emoji: The emoji to get
            limit: Max number of users to return (1-100)
            after: Get users after this user ID

        Returns:
            list of users who have reacted with that emoji

        """
        reaction_data = await self._client.http.get_reactions(
            self._channel_id, self.id, emoji, limit, to_optional_snowflake(after)
        )
        return [self._client.cache.place_user_data(user_data) for user_data in reaction_data]

    async def add_reaction(self, emoji: Union["models.PartialEmoji", dict, str]) -> None:
        """
        Add a reaction to this message.

        Args:
            emoji: the emoji to react with

        """
        emoji = models.process_emoji_req_format(emoji)
        await self._client.http.create_reaction(self._channel_id, self.id, emoji)

    async def remove_reaction(
        self,
        emoji: Union["models.PartialEmoji", dict, str],
        member: Optional[Union["models.Member", "models.User", "Snowflake_Type"]] = MISSING,
    ) -> None:
        """
        Remove a specific reaction that a user reacted with.

        Args:
            emoji: Emoji to remove
            member: Member to remove reaction of. Default's to NAFF bot user.

        """
        emoji_str = models.process_emoji_req_format(emoji)
        if not member:
            member = self._client.user
        user_id = to_snowflake(member)
        if user_id == self._client.user.id:
            await self._client.http.remove_self_reaction(self._channel_id, self.id, emoji_str)
        else:
            await self._client.http.remove_user_reaction(self._channel_id, self.id, emoji_str, user_id)

    async def clear_reactions(self, emoji: Union["models.PartialEmoji", dict, str]) -> None:
        """
        Clear a specific reaction from message.

        Args:
            emoji: The emoji to clear

        """
        emoji = models.process_emoji_req_format(emoji)
        await self._client.http.clear_reaction(self._channel_id, self.id, emoji)

    async def clear_all_reactions(self) -> None:
        """Clear all emojis from a message."""
        await self._client.http.clear_reactions(self._channel_id, self.id)

    async def pin(self) -> None:
        """Pin message."""
        await self._client.http.pin_message(self._channel_id, self.id)
        self.pinned = True

    async def unpin(self) -> None:
        """Unpin message."""
        await self._client.http.unpin_message(self._channel_id, self.id)
        self.pinned = False

    async def publish(self) -> None:
        """
        Publish this message.

        (Discord api calls it "crosspost")

        """
        await self._client.http.crosspost_message(self._channel_id, self.id)

content: str = field(default=MISSING) class-attribute

Contents of the message

timestamp: models.Timestamp = field(default=MISSING, converter=optional_c(timestamp_converter)) class-attribute

When this message was sent

edited_timestamp: Optional[models.Timestamp] = field(default=None, converter=optional_c(timestamp_converter)) class-attribute

When this message was edited (or None if never)

tts: bool = field(default=False) class-attribute

Whether this was a TTS message

mention_everyone: bool = field(default=False) class-attribute

Whether this message mentions everyone

mention_channels: List[ChannelMention] = field(factory=list) class-attribute

Channels specifically mentioned in this message

attachments: List[Attachment] = field(factory=list) class-attribute

Any attached files

embeds: List[models.Embed] = field(factory=list) class-attribute

Any embedded content

reactions: List[models.Reaction] = field(factory=list) class-attribute

Reactions to the message

nonce: Optional[Union[int, str]] = field(default=None) class-attribute

Used for validating a message was sent

pinned: bool = field(default=False) class-attribute

Whether this message is pinned

webhook_id: Optional[Snowflake_Type] = field(default=None, converter=to_optional_snowflake) class-attribute

If the message is generated by a webhook, this is the webhook's id

type: MessageTypes = field(default=MISSING, converter=optional_c(MessageTypes)) class-attribute

Type of message

activity: Optional[MessageActivity] = field(default=None, converter=optional_c(MessageActivity)) class-attribute

Activity sent with Rich Presence-related chat embeds

application: Optional[models.Application] = field(default=None) class-attribute

Application sent with Rich Presence-related chat embeds

application_id: Optional[Snowflake_Type] = field(default=None, converter=to_optional_snowflake) class-attribute

If the message is an Interaction or application-owned webhook, this is the id of the application

message_reference: Optional[MessageReference] = field(default=None, converter=optional_c(MessageReference.from_dict)) class-attribute

Data showing the source of a crosspost, channel follow add, pin, or reply message

flags: MessageFlags = field(default=MessageFlags.NONE, converter=MessageFlags) class-attribute

Message flags combined as a bitfield

interaction: Optional[MessageInteraction] = field(default=None) class-attribute

Sent if the message is a response to an Interaction

components: Optional[List[models.ActionRow]] = field(default=None) class-attribute

Sent if the message contains components like buttons, action rows, or other interactive components

sticker_items: Optional[List[models.StickerItem]] = field(default=None) class-attribute

Sent if the message contains stickers

mention_users() async property

A generator of users mentioned in this message

Source code in naff/models/discord/message.py
320
321
322
323
324
@property
async def mention_users(self) -> AsyncGenerator["models.Member", None]:
    """A generator of users mentioned in this message"""
    for u_id in self._mention_ids:
        yield await self._client.cache.fetch_member(self._guild_id, u_id)

mention_roles() async property

A generator of roles mentioned in this message

Source code in naff/models/discord/message.py
326
327
328
329
330
@property
async def mention_roles(self) -> AsyncGenerator["models.Role", None]:
    """A generator of roles mentioned in this message"""
    for r_id in self._mention_roles:
        yield await self._client.cache.fetch_role(self._guild_id, r_id)

thread() property

The thread that was started from this message, if any

Source code in naff/models/discord/message.py
332
333
334
335
@property
def thread(self) -> "models.TYPE_THREAD_CHANNEL":
    """The thread that was started from this message, if any"""
    return self._client.cache.get_channel(self.id)

fetch_referenced_message() async

Fetch the message this message is referencing, if any.

Returns:

Type Description
Optional[Message]

The referenced message, if found

Source code in naff/models/discord/message.py
337
338
339
340
341
342
343
344
345
346
347
async def fetch_referenced_message(self) -> Optional["Message"]:
    """
    Fetch the message this message is referencing, if any.

    Returns:
        The referenced message, if found

    """
    if self._referenced_message_id is None:
        return None
    return await self._client.cache.fetch_message(self._channel_id, self._referenced_message_id)

get_referenced_message()

Get the message this message is referencing, if any.

Returns:

Type Description
Optional[Message]

The referenced message, if found

Source code in naff/models/discord/message.py
349
350
351
352
353
354
355
356
357
358
359
def get_referenced_message(self) -> Optional["Message"]:
    """
    Get the message this message is referencing, if any.

    Returns:
        The referenced message, if found

    """
    if self._referenced_message_id is None:
        return None
    return self._client.cache.get_message(self._channel_id, self._referenced_message_id)

system_content() property

Content for system messages. (boosts, welcomes, etc)

Source code in naff/models/discord/message.py
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
@property
def system_content(self) -> Optional[str]:
    """Content for system messages. (boosts, welcomes, etc)"""
    match self.type:
        case MessageTypes.USER_PREMIUM_GUILD_SUBSCRIPTION:
            return f"{self.author.mention} just boosted the server!"
        case MessageTypes.USER_PREMIUM_GUILD_SUBSCRIPTION_TIER_1:
            return f"{self.author.mention} just boosted the server! {self.guild.name} has achieved **Level 1!**"
        case MessageTypes.USER_PREMIUM_GUILD_SUBSCRIPTION_TIER_2:
            return f"{self.author.mention} just boosted the server! {self.guild.name} has achieved **Level 2!**"
        case MessageTypes.USER_PREMIUM_GUILD_SUBSCRIPTION_TIER_3:
            return f"{self.author.mention} just boosted the server! {self.guild.name} has achieved **Level 3!**"
        case MessageTypes.GUILD_MEMBER_JOIN:
            return GUILD_WELCOME_MESSAGES[
                int(self.timestamp.timestamp() * 1000)
                % len(GUILD_WELCOME_MESSAGES)
                # This is how Discord calculates the welcome message.
            ].format(self.author.mention)
        case MessageTypes.THREAD_CREATED:
            return f"{self.author.mention} started a thread: {self.thread.mention}. See all **threads**."
        case MessageTypes.CHANNEL_FOLLOW_ADD:
            return f"{self.author.mention} has added **{self.content}** to this channel. Its most important updates will show up here."
        case MessageTypes.RECIPIENT_ADD:
            return f"{self.author.mention} added <@{self._mention_ids[0]}> to the thread."
        case MessageTypes.RECIPIENT_REMOVE:
            return f"{self.author.mention} removed <@{self._mention_ids[0]}> from the thread."
        case MessageTypes.CHANNEL_NAME_CHANGE:
            return f"{self.author.mention} changed the channel name: **{self.content}**."
        case MessageTypes.CHANNEL_PINNED_MESSAGE:
            return f"{self.author.mention} pinned a message. See all pinned messages"
        case MessageTypes.GUILD_DISCOVERY_DISQUALIFIED:
            return "This server has been removed from Server Discovery because it no longer passes all the requirements. Check Server Settings for more details."
        case MessageTypes.GUILD_DISCOVERY_REQUALIFIED:
            return "This server is eligible for Server Discovery again and has been automatically relisted!"
        case MessageTypes.GUILD_DISCOVERY_GRACE_PERIOD_INITIAL_WARNING:
            return "This server has failed Discovery activity requirements for 1 week. If this server fails for 4 weeks in a row, it will be automatically removed from Discovery."
        case MessageTypes.GUILD_DISCOVERY_GRACE_PERIOD_FINAL_WARNING:
            return "This server has failed Discovery activity requirements for 3 weeks in a row. If this server fails for 1 more week, it will be removed from Discovery."
        case MessageTypes.GUILD_INVITE_REMINDER:
            return "**Invite your friends**\nThe best way to setup a server is with your buddies!"
        case MessageTypes.THREAD_STARTER_MESSAGE:
            if referenced_message := self.get_referenced_message():
                return referenced_message.content
            return "Sorry, we couldn't load the first message in this thread"
        case MessageTypes.AUTO_MODERATION_ACTION:
            keyword_matched_content = self.embeds[0].fields[4].value  # The words that triggered the action
            message_content = self.embeds[0].description.replace(
                keyword_matched_content, f"**{keyword_matched_content}**"
            )
            rule = self.embeds[0].fields[0].value  # What rule was triggered
            channel = self.embeds[0].fields[1].value  # Channel that the action took place in
            return f'AutoMod has blocked a message in <#{channel}>. "{message_content}" from {self.author.mention}. Rule: {rule}.'
        case _:
            return None

jump_url() property

A url that allows the client to jump to this message.

Source code in naff/models/discord/message.py
494
495
496
497
@property
def jump_url(self) -> str:
    """A url that allows the client to *jump* to this message."""
    return f"https://discord.com/channels/{self._guild_id or '@me'}/{self._channel_id}/{self.id}"

proto_url() property

A URL like jump_url that uses protocols.

Source code in naff/models/discord/message.py
499
500
501
502
@property
def proto_url(self) -> str:
    """A URL like `jump_url` that uses protocols."""
    return f"discord://-/channels/{self._guild_id or '@me'}/{self._channel_id}/{self.id}"

edit(content=None, embeds=None, embed=None, components=None, allowed_mentions=None, attachments=None, files=None, file=None, tts=False, flags=None) async

Edits the message.

Parameters:

Name Type Description Default
content Optional[str]

Message text content.

None
embeds Optional[Union[Sequence[Union[Embed, dict]], Union[Embed, dict]]]

Embedded rich content (up to 6000 characters).

None
embed Optional[Union[Embed, dict]]

Embedded rich content (up to 6000 characters).

None
components Optional[Union[Sequence[Sequence[Union[BaseComponent, dict]]], Sequence[Union[BaseComponent, dict]], BaseComponent, dict]]

The components to include with the message.

None
allowed_mentions Optional[Union[AllowedMentions, dict]]

Allowed mentions for the message.

None
attachments Optional[Optional[Sequence[Union[Attachment, dict]]]]

The attachments to keep, only used when editing message.

None
files Optional[Union[UPLOADABLE_TYPE, Sequence[UPLOADABLE_TYPE]]]

Files to send, the path, bytes or File() instance, defaults to None. You may have up to 10 files.

None
file Optional[UPLOADABLE_TYPE]

Files to send, the path, bytes or File() instance, defaults to None. You may have up to 10 files.

None
tts bool

Should this message use Text To Speech.

False
flags Optional[Union[int, MessageFlags]]

Message flags to apply.

None

Returns:

Type Description
Message

New message object with edits applied

Source code in naff/models/discord/message.py
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
async def edit(
    self,
    content: Optional[str] = None,
    embeds: Optional[Union[Sequence[Union["models.Embed", dict]], Union["models.Embed", dict]]] = None,
    embed: Optional[Union["models.Embed", dict]] = None,
    components: Optional[
        Union[
            Sequence[Sequence[Union["models.BaseComponent", dict]]],
            Sequence[Union["models.BaseComponent", dict]],
            "models.BaseComponent",
            dict,
        ]
    ] = None,
    allowed_mentions: Optional[Union[AllowedMentions, dict]] = None,
    attachments: Optional[Optional[Sequence[Union[Attachment, dict]]]] = None,
    files: Optional[Union[UPLOADABLE_TYPE, Sequence[UPLOADABLE_TYPE]]] = None,
    file: Optional[UPLOADABLE_TYPE] = None,
    tts: bool = False,
    flags: Optional[Union[int, MessageFlags]] = None,
) -> "Message":
    """
    Edits the message.

    Args:
        content: Message text content.
        embeds: Embedded rich content (up to 6000 characters).
        embed: Embedded rich content (up to 6000 characters).
        components: The components to include with the message.
        allowed_mentions: Allowed mentions for the message.
        attachments: The attachments to keep, only used when editing message.
        files: Files to send, the path, bytes or File() instance, defaults to None. You may have up to 10 files.
        file: Files to send, the path, bytes or File() instance, defaults to None. You may have up to 10 files.
        tts: Should this message use Text To Speech.
        flags: Message flags to apply.

    Returns:
        New message object with edits applied

    """
    message_payload = process_message_payload(
        content=content,
        embeds=embeds or embed,
        components=components,
        allowed_mentions=allowed_mentions,
        attachments=attachments,
        tts=tts,
        flags=flags,
    )

    if self.flags == MessageFlags.EPHEMERAL:
        raise EphemeralEditException

    message_data = await self._client.http.edit_message(message_payload, self._channel_id, self.id, files=files)
    if message_data:
        return self._client.cache.place_message_data(message_data)

delete(delay=MISSING) async

Delete message.

Parameters:

Name Type Description Default
delay Absent[Optional[int]]

Seconds to wait before deleting message.

MISSING
Source code in naff/models/discord/message.py
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
async def delete(self, delay: Absent[Optional[int]] = MISSING) -> None:
    """
    Delete message.

    Args:
        delay: Seconds to wait before deleting message.

    """
    if delay and delay > 0:

        async def delayed_delete() -> None:
            await asyncio.sleep(delay)
            try:
                await self._client.http.delete_message(self._channel_id, self.id)
            except Exception:  # noqa: S110
                pass  # No real way to handle this

        asyncio.create_task(delayed_delete())

    else:
        await self._client.http.delete_message(self._channel_id, self.id)

reply(content=None, embeds=None, embed=None, **kwargs) async

Reply to this message, takes all the same attributes as send.

Parameters:

Name Type Description Default
content Optional[str]

Message text content.

None
embeds Optional[Union[List[Union[Embed, dict]], Union[Embed, dict]]]

Embedded rich content (up to 6000 characters).

None
embed Optional[Union[Embed, dict]]

Embedded rich content (up to 6000 characters).

None
**kwargs Mapping[str, Any]

Additional options to pass to send.

{}

Returns:

Type Description
Message

New message object.

Source code in naff/models/discord/message.py
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
async def reply(
    self,
    content: Optional[str] = None,
    embeds: Optional[Union[List[Union["models.Embed", dict]], Union["models.Embed", dict]]] = None,
    embed: Optional[Union["models.Embed", dict]] = None,
    **kwargs: Mapping[str, Any],
) -> "Message":
    """
    Reply to this message, takes all the same attributes as `send`.

    Args:
        content: Message text content.
        embeds: Embedded rich content (up to 6000 characters).
        embed: Embedded rich content (up to 6000 characters).
        **kwargs: Additional options to pass to `send`.

    Returns:
        New message object.

    """
    return await self.channel.send(content=content, reply_to=self, embeds=embeds or embed, **kwargs)

create_thread(name, auto_archive_duration=AutoArchiveDuration.ONE_DAY, reason=None) async

Create a thread from this message.

Parameters:

Name Type Description Default
name str

The name of this thread

required
auto_archive_duration Union[AutoArchiveDuration, int]

duration in minutes to automatically archive the thread after recent activity, can be set to: 60, 1440, 4320, 10080

AutoArchiveDuration.ONE_DAY
reason Optional[str]

The optional reason for creating this thread

None

Returns:

Type Description
TYPE_THREAD_CHANNEL

The created thread object

Raises:

Type Description
ThreadOutsideOfGuild

if this is invoked on a message outside of a guild

Source code in naff/models/discord/message.py
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
async def create_thread(
    self,
    name: str,
    auto_archive_duration: Union[AutoArchiveDuration, int] = AutoArchiveDuration.ONE_DAY,
    reason: Optional[str] = None,
) -> "models.TYPE_THREAD_CHANNEL":
    """
    Create a thread from this message.

    Args:
        name: The name of this thread
        auto_archive_duration: duration in minutes to automatically archive the thread after recent activity, can be set to: 60, 1440, 4320, 10080
        reason: The optional reason for creating this thread

    Returns:
        The created thread object

    Raises:
        ThreadOutsideOfGuild: if this is invoked on a message outside of a guild

    """
    if self.channel.type not in (ChannelTypes.GUILD_TEXT, ChannelTypes.GUILD_NEWS):
        raise ThreadOutsideOfGuild

    thread_data = await self._client.http.create_thread(
        channel_id=self._channel_id,
        name=name,
        auto_archive_duration=auto_archive_duration,
        message_id=self.id,
        reason=reason,
    )
    return self._client.cache.place_channel_data(thread_data)

suppress_embeds() async

Suppress embeds for this message.

Note

Requires the Permissions.MANAGE_MESSAGES permission.

Source code in naff/models/discord/message.py
637
638
639
640
641
642
643
644
645
646
647
648
649
async def suppress_embeds(self) -> "Message":
    """
    Suppress embeds for this message.

    !!! note
        Requires the `Permissions.MANAGE_MESSAGES` permission.

    """
    message_data = await self._client.http.edit_message(
        {"flags": MessageFlags.SUPPRESS_EMBEDS}, self._channel_id, self.id
    )
    if message_data:
        return self._client.cache.place_message_data(message_data)

fetch_reaction(emoji, limit=MISSING, after=MISSING) async

Fetches reactions of a specific emoji from this message.

Parameters:

Name Type Description Default
emoji Union[PartialEmoji, dict, str]

The emoji to get

required
limit Absent[int]

Max number of users to return (1-100)

MISSING
after Absent[Snowflake_Type]

Get users after this user ID

MISSING

Returns:

Type Description
List[User]

list of users who have reacted with that emoji

Source code in naff/models/discord/message.py
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
async def fetch_reaction(
    self,
    emoji: Union["models.PartialEmoji", dict, str],
    limit: Absent[int] = MISSING,
    after: Absent["Snowflake_Type"] = MISSING,
) -> List["models.User"]:
    """
    Fetches reactions of a specific emoji from this message.

    Args:
        emoji: The emoji to get
        limit: Max number of users to return (1-100)
        after: Get users after this user ID

    Returns:
        list of users who have reacted with that emoji

    """
    reaction_data = await self._client.http.get_reactions(
        self._channel_id, self.id, emoji, limit, to_optional_snowflake(after)
    )
    return [self._client.cache.place_user_data(user_data) for user_data in reaction_data]

add_reaction(emoji) async

Add a reaction to this message.

Parameters:

Name Type Description Default
emoji Union[PartialEmoji, dict, str]

the emoji to react with

required
Source code in naff/models/discord/message.py
674
675
676
677
678
679
680
681
682
683
async def add_reaction(self, emoji: Union["models.PartialEmoji", dict, str]) -> None:
    """
    Add a reaction to this message.

    Args:
        emoji: the emoji to react with

    """
    emoji = models.process_emoji_req_format(emoji)
    await self._client.http.create_reaction(self._channel_id, self.id, emoji)

remove_reaction(emoji, member=MISSING) async

Remove a specific reaction that a user reacted with.

Parameters:

Name Type Description Default
emoji Union[PartialEmoji, dict, str]

Emoji to remove

required
member Optional[Union[Member, User, Snowflake_Type]]

Member to remove reaction of. Default's to NAFF bot user.

MISSING
Source code in naff/models/discord/message.py
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
async def remove_reaction(
    self,
    emoji: Union["models.PartialEmoji", dict, str],
    member: Optional[Union["models.Member", "models.User", "Snowflake_Type"]] = MISSING,
) -> None:
    """
    Remove a specific reaction that a user reacted with.

    Args:
        emoji: Emoji to remove
        member: Member to remove reaction of. Default's to NAFF bot user.

    """
    emoji_str = models.process_emoji_req_format(emoji)
    if not member:
        member = self._client.user
    user_id = to_snowflake(member)
    if user_id == self._client.user.id:
        await self._client.http.remove_self_reaction(self._channel_id, self.id, emoji_str)
    else:
        await self._client.http.remove_user_reaction(self._channel_id, self.id, emoji_str, user_id)

clear_reactions(emoji) async

Clear a specific reaction from message.

Parameters:

Name Type Description Default
emoji Union[PartialEmoji, dict, str]

The emoji to clear

required
Source code in naff/models/discord/message.py
707
708
709
710
711
712
713
714
715
716
async def clear_reactions(self, emoji: Union["models.PartialEmoji", dict, str]) -> None:
    """
    Clear a specific reaction from message.

    Args:
        emoji: The emoji to clear

    """
    emoji = models.process_emoji_req_format(emoji)
    await self._client.http.clear_reaction(self._channel_id, self.id, emoji)

clear_all_reactions() async

Clear all emojis from a message.

Source code in naff/models/discord/message.py
718
719
720
async def clear_all_reactions(self) -> None:
    """Clear all emojis from a message."""
    await self._client.http.clear_reactions(self._channel_id, self.id)

pin() async

Pin message.

Source code in naff/models/discord/message.py
722
723
724
725
async def pin(self) -> None:
    """Pin message."""
    await self._client.http.pin_message(self._channel_id, self.id)
    self.pinned = True

unpin() async

Unpin message.

Source code in naff/models/discord/message.py
727
728
729
730
async def unpin(self) -> None:
    """Unpin message."""
    await self._client.http.unpin_message(self._channel_id, self.id)
    self.pinned = False

publish() async

Publish this message.

(Discord api calls it "crosspost")

Source code in naff/models/discord/message.py
732
733
734
735
736
737
738
739
async def publish(self) -> None:
    """
    Publish this message.

    (Discord api calls it "crosspost")

    """
    await self._client.http.crosspost_message(self._channel_id, self.id)

MessageTypes

Bases: CursedIntEnum

Types of message.

Source code in naff/models/discord/enums.py
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
class MessageTypes(CursedIntEnum):
    """Types of message."""

    DEFAULT = 0
    RECIPIENT_ADD = 1
    RECIPIENT_REMOVE = 2
    CALL = 3
    CHANNEL_NAME_CHANGE = 4
    CHANNEL_ICON_CHANGE = 5
    CHANNEL_PINNED_MESSAGE = 6
    GUILD_MEMBER_JOIN = 7
    USER_PREMIUM_GUILD_SUBSCRIPTION = 8
    USER_PREMIUM_GUILD_SUBSCRIPTION_TIER_1 = 9
    USER_PREMIUM_GUILD_SUBSCRIPTION_TIER_2 = 10
    USER_PREMIUM_GUILD_SUBSCRIPTION_TIER_3 = 11
    CHANNEL_FOLLOW_ADD = 12
    GUILD_DISCOVERY_DISQUALIFIED = 14
    GUILD_DISCOVERY_REQUALIFIED = 15
    GUILD_DISCOVERY_GRACE_PERIOD_INITIAL_WARNING = 16
    GUILD_DISCOVERY_GRACE_PERIOD_FINAL_WARNING = 17
    THREAD_CREATED = 18
    REPLY = 19
    APPLICATION_COMMAND = 20
    THREAD_STARTER_MESSAGE = 21
    GUILD_INVITE_REMINDER = 22
    CONTEXT_MENU_COMMAND = 23
    AUTO_MODERATION_ACTION = 24

process_allowed_mentions(allowed_mentions)

Process allowed mentions into a dictionary.

Parameters:

Name Type Description Default
allowed_mentions Optional[Union[AllowedMentions, dict]]

Allowed mentions object or dictionary

required

Returns:

Type Description
Optional[dict]

Dictionary of allowed mentions

Raises:

Type Description
ValueError

Invalid allowed mentions

Source code in naff/models/discord/message.py
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
def process_allowed_mentions(allowed_mentions: Optional[Union[AllowedMentions, dict]]) -> Optional[dict]:
    """
    Process allowed mentions into a dictionary.

    Args:
        allowed_mentions: Allowed mentions object or dictionary

    Returns:
        Dictionary of allowed mentions

    Raises:
        ValueError: Invalid allowed mentions

    """
    if not allowed_mentions:
        return allowed_mentions

    if isinstance(allowed_mentions, dict):
        return allowed_mentions

    if isinstance(allowed_mentions, AllowedMentions):
        return allowed_mentions.to_dict()

    raise ValueError(f"Invalid allowed mentions: {allowed_mentions}")

process_message_reference(message_reference)

Process mention references into a dictionary.

Parameters:

Name Type Description Default
message_reference Optional[Union[MessageReference, Message, dict, Snowflake_Type]]

Message reference object

required

Returns:

Type Description
Optional[dict]

Message reference dictionary

Raises:

Type Description
ValueError

Invalid message reference

Source code in naff/models/discord/message.py
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
def process_message_reference(
    message_reference: Optional[Union[MessageReference, Message, dict, "Snowflake_Type"]]
) -> Optional[dict]:
    """
    Process mention references into a dictionary.

    Args:
        message_reference: Message reference object

    Returns:
        Message reference dictionary

    Raises:
        ValueError: Invalid message reference

    """
    if not message_reference:
        return message_reference

    if isinstance(message_reference, dict):
        return message_reference

    if isinstance(message_reference, (str, int)):
        message_reference = MessageReference(message_id=message_reference)

    if isinstance(message_reference, Message):
        message_reference = MessageReference.for_message(message_reference)

    if isinstance(message_reference, MessageReference):
        return message_reference.to_dict()

    raise ValueError(f"Invalid message reference: {message_reference}")

process_message_payload(content=None, embeds=None, components=None, stickers=None, allowed_mentions=None, reply_to=None, attachments=None, tts=False, flags=None, **kwargs)

Format message content for it to be ready to send discord.

Parameters:

Name Type Description Default
content Optional[str]

Message text content.

None
embeds Optional[Union[List[Union[Embed, dict]], Union[Embed, dict]]]

Embedded rich content (up to 6000 characters).

None
components Optional[Union[List[List[Union[BaseComponent, dict]]], List[Union[BaseComponent, dict]], BaseComponent, dict]]

The components to include with the message.

None
stickers Optional[Union[List[Union[Sticker, Snowflake_Type]], Sticker, Snowflake_Type]]

IDs of up to 3 stickers in the server to send in the message.

None
allowed_mentions Optional[Union[AllowedMentions, dict]]

Allowed mentions for the message.

None
reply_to Optional[Union[MessageReference, Message, dict, Snowflake_Type]]

Message to reference, must be from the same channel.

None
attachments Optional[List[Union[Attachment, dict]]]

The attachments to keep, only used when editing message.

None
tts bool

Should this message use Text To Speech.

False
flags Optional[Union[int, MessageFlags]]

Message flags to apply.

None

Returns:

Type Description
dict

Dictionary

Source code in naff/models/discord/message.py
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
def process_message_payload(
    content: Optional[str] = None,
    embeds: Optional[Union[List[Union["models.Embed", dict]], Union["models.Embed", dict]]] = None,
    components: Optional[
        Union[
            List[List[Union["models.BaseComponent", dict]]],
            List[Union["models.BaseComponent", dict]],
            "models.BaseComponent",
            dict,
        ]
    ] = None,
    stickers: Optional[
        Union[List[Union["models.Sticker", "Snowflake_Type"]], "models.Sticker", "Snowflake_Type"]
    ] = None,
    allowed_mentions: Optional[Union[AllowedMentions, dict]] = None,
    reply_to: Optional[Union[MessageReference, Message, dict, "Snowflake_Type"]] = None,
    attachments: Optional[List[Union[Attachment, dict]]] = None,
    tts: bool = False,
    flags: Optional[Union[int, MessageFlags]] = None,
    **kwargs,
) -> dict:
    """
    Format message content for it to be ready to send discord.

    Args:
        content: Message text content.
        embeds: Embedded rich content (up to 6000 characters).
        components: The components to include with the message.
        stickers: IDs of up to 3 stickers in the server to send in the message.
        allowed_mentions: Allowed mentions for the message.
        reply_to: Message to reference, must be from the same channel.
        attachments: The attachments to keep, only used when editing message.
        tts: Should this message use Text To Speech.
        flags: Message flags to apply.

    Returns:
        Dictionary

    """
    embeds = models.process_embeds(embeds)
    if isinstance(embeds, list):
        embeds = embeds if all(e is not None for e in embeds) else None

    components = models.process_components(components)
    if stickers:
        stickers = [to_snowflake(sticker) for sticker in stickers]
    allowed_mentions = process_allowed_mentions(allowed_mentions)
    message_reference = process_message_reference(reply_to)
    if attachments:
        attachments = [attachment.to_dict() for attachment in attachments]

    return dict_filter_none(
        {
            "content": content,
            "embeds": embeds,
            "components": components,
            "sticker_ids": stickers,
            "allowed_mentions": allowed_mentions,
            "message_reference": message_reference,
            "attachments": attachments,
            "tts": tts,
            "flags": flags,
            **kwargs,
        }
    )

ReactionUsers

Bases: AsyncIterator

An async iterator for searching through a channel's history.

Attributes:

Name Type Description
reaction Reaction

The reaction to search through

limit Reaction

The maximum number of users to return (set to 0 for no limit)

after Snowflake_Type

get users after this message ID

Source code in naff/models/discord/reaction.py
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
class ReactionUsers(AsyncIterator):
    """
    An async iterator for searching through a channel's history.

    Attributes:
        reaction: The reaction to search through
        limit: The maximum number of users to return (set to 0 for no limit)
        after: get users after this message ID

    """

    def __init__(self, reaction: "Reaction", limit: int = 50, after: Optional["Snowflake_Type"] = None) -> None:
        self.reaction: "Reaction" = reaction
        self.after: "Snowflake_Type" = after
        self._more = True
        super().__init__(limit)

    async def fetch(self) -> List["User"]:
        """
        Gets all the users who reacted to the message. Requests user data from discord API if not cached.

        Returns:
            A list of users who reacted to the message.

        """
        if self._more:
            expected = self.get_limit

            if self.after and not self.last:
                self.last = namedtuple("temp", "id")
                self.last.id = self.after

            users = await self.reaction._client.http.get_reactions(
                self.reaction._channel_id,
                self.reaction._message_id,
                self.reaction.emoji.req_format,
                limit=expected,
                after=self.last.id or MISSING,
            )
            if not users:
                raise QueueEmpty
            self._more = len(users) == expected
            return [self.reaction._client.cache.place_user_data(u) for u in users]
        else:
            raise QueueEmpty

fetch() async

Gets all the users who reacted to the message. Requests user data from discord API if not cached.

Returns:

Type Description
List[User]

A list of users who reacted to the message.

Source code in naff/models/discord/reaction.py
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
async def fetch(self) -> List["User"]:
    """
    Gets all the users who reacted to the message. Requests user data from discord API if not cached.

    Returns:
        A list of users who reacted to the message.

    """
    if self._more:
        expected = self.get_limit

        if self.after and not self.last:
            self.last = namedtuple("temp", "id")
            self.last.id = self.after

        users = await self.reaction._client.http.get_reactions(
            self.reaction._channel_id,
            self.reaction._message_id,
            self.reaction.emoji.req_format,
            limit=expected,
            after=self.last.id or MISSING,
        )
        if not users:
            raise QueueEmpty
        self._more = len(users) == expected
        return [self.reaction._client.cache.place_user_data(u) for u in users]
    else:
        raise QueueEmpty

Reaction

Bases: ClientObject

Source code in naff/models/discord/reaction.py
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
@define()
class Reaction(ClientObject):
    count: int = field()
    """times this emoji has been used to react"""
    me: bool = field(default=False)
    """whether the current user reacted using this emoji"""
    emoji: "PartialEmoji" = field(converter=PartialEmoji.from_dict)
    """emoji information"""

    _channel_id: "Snowflake_Type" = field(converter=to_snowflake)
    _message_id: "Snowflake_Type" = field(converter=to_snowflake)

    def users(self, limit: int = 0, after: "Snowflake_Type" = None) -> ReactionUsers:
        """Users who reacted using this emoji."""
        return ReactionUsers(self, limit, after)

    @property
    def message(self) -> "Message":
        """The message this reaction is on."""
        return self._client.cache.get_message(self._channel_id, self._message_id)

    @property
    def channel(self) -> "TYPE_ALL_CHANNEL":
        """The channel this reaction is on."""
        return self._client.cache.get_channel(self._channel_id)

    async def remove(self) -> None:
        """Remove all this emoji's reactions from the message."""
        await self._client.http.clear_reaction(self._channel_id, self._message_id, self.emoji.req_format)

count: int = field() class-attribute

times this emoji has been used to react

me: bool = field(default=False) class-attribute

whether the current user reacted using this emoji

emoji: PartialEmoji = field(converter=PartialEmoji.from_dict) class-attribute

emoji information

users(limit=0, after=None)

Users who reacted using this emoji.

Source code in naff/models/discord/reaction.py
79
80
81
def users(self, limit: int = 0, after: "Snowflake_Type" = None) -> ReactionUsers:
    """Users who reacted using this emoji."""
    return ReactionUsers(self, limit, after)

message() property

The message this reaction is on.

Source code in naff/models/discord/reaction.py
83
84
85
86
@property
def message(self) -> "Message":
    """The message this reaction is on."""
    return self._client.cache.get_message(self._channel_id, self._message_id)

channel() property

The channel this reaction is on.

Source code in naff/models/discord/reaction.py
88
89
90
91
@property
def channel(self) -> "TYPE_ALL_CHANNEL":
    """The channel this reaction is on."""
    return self._client.cache.get_channel(self._channel_id)

remove() async

Remove all this emoji's reactions from the message.

Source code in naff/models/discord/reaction.py
93
94
95
async def remove(self) -> None:
    """Remove all this emoji's reactions from the message."""
    await self._client.http.clear_reaction(self._channel_id, self._message_id, self.emoji.req_format)

UX.

EmbedField

Bases: DictSerializationMixin

Representation of an embed field.

Attributes:

Name Type Description
name str

Field name

value str

Field value

inline bool

If the field should be inline

Source code in naff/models/discord/embed.py
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
@define(kw_only=False)
class EmbedField(DictSerializationMixin):
    """
    Representation of an embed field.

    Attributes:
        name: Field name
        value: Field value
        inline: If the field should be inline

    """

    name: str = field()
    value: str = field()
    inline: bool = field(default=False)

    @name.validator
    def _name_validation(self, attribute: str, value: Any) -> None:
        if len(value) > EMBED_MAX_NAME_LENGTH:
            raise ValueError(f"Field name cannot exceed {EMBED_MAX_NAME_LENGTH} characters")

    @value.validator
    def _value_validation(self, attribute: str, value: Any) -> None:
        if len(value) > EMBED_FIELD_VALUE_LENGTH:
            raise ValueError(f"Field value cannot exceed {EMBED_FIELD_VALUE_LENGTH} characters")

    def __len__(self) -> int:
        return len(self.name) + len(self.value)

EmbedAuthor

Bases: DictSerializationMixin

Representation of an embed author.

Attributes:

Name Type Description
name Optional[str]

Name to show on embed

url Optional[str]

Url to go to when name is clicked

icon_url Optional[str]

Icon to show next to name

proxy_icon_url Optional[str]

Proxy icon url

Source code in naff/models/discord/embed.py
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
@define(kw_only=False)
class EmbedAuthor(DictSerializationMixin):
    """
    Representation of an embed author.

    Attributes:
        name: Name to show on embed
        url: Url to go to when name is clicked
        icon_url: Icon to show next to name
        proxy_icon_url: Proxy icon url

    """

    name: Optional[str] = field(default=None)
    url: Optional[str] = field(default=None)
    icon_url: Optional[str] = field(default=None)
    proxy_icon_url: Optional[str] = field(default=None, metadata=no_export_meta)

    @name.validator
    def _name_validation(self, attribute: str, value: Any) -> None:
        if len(value) > EMBED_MAX_NAME_LENGTH:
            raise ValueError(f"Field name cannot exceed {EMBED_MAX_NAME_LENGTH} characters")

    def __len__(self) -> int:
        return len(self.name)

EmbedAttachment

Bases: DictSerializationMixin

Representation of an attachment.

Attributes:

Name Type Description
url Optional[str]

Attachment url

proxy_url Optional[str]

Proxy url

height Optional[int]

Attachment height

width Optional[int]

Attachment width

Source code in naff/models/discord/embed.py
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
@define(kw_only=False)
class EmbedAttachment(DictSerializationMixin):  # thumbnail or image or video
    """
    Representation of an attachment.

    Attributes:
        url: Attachment url
        proxy_url: Proxy url
        height: Attachment height
        width: Attachment width

    """

    url: Optional[str] = field(default=None)
    proxy_url: Optional[str] = field(default=None, metadata=no_export_meta)
    height: Optional[int] = field(default=None, metadata=no_export_meta)
    width: Optional[int] = field(default=None, metadata=no_export_meta)

    @classmethod
    def _process_dict(cls, data: Dict[str, Any]) -> Dict[str, Any]:
        if isinstance(data, str):
            return {"url": data}
        return data

    @property
    def size(self) -> tuple[Optional[int], Optional[int]]:
        return self.height, self.width

EmbedFooter

Bases: DictSerializationMixin

Representation of an Embed Footer.

Attributes:

Name Type Description
text str

Footer text

icon_url Optional[str]

Footer icon url

proxy_icon_url Optional[str]

Proxy icon url

Source code in naff/models/discord/embed.py
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
@define(kw_only=False)
class EmbedFooter(DictSerializationMixin):
    """
    Representation of an Embed Footer.

    Attributes:
        text: Footer text
        icon_url: Footer icon url
        proxy_icon_url: Proxy icon url

    """

    text: str = field()
    icon_url: Optional[str] = field(default=None)
    proxy_icon_url: Optional[str] = field(default=None, metadata=no_export_meta)

    @classmethod
    def converter(cls, ingest: Union[dict, str, "EmbedFooter"]) -> "EmbedFooter":
        """
        A converter to handle users passing raw strings or dictionaries as footers to the Embed object.

        Args:
            ingest: The data to convert

        Returns:
            An EmbedFooter object
        """
        if isinstance(ingest, str):
            return cls(text=ingest)
        else:
            return cls.from_dict(ingest)

    def __len__(self) -> int:
        return len(self.text)

converter(ingest) classmethod

A converter to handle users passing raw strings or dictionaries as footers to the Embed object.

Parameters:

Name Type Description Default
ingest Union[dict, str, EmbedFooter]

The data to convert

required

Returns:

Type Description
EmbedFooter

An EmbedFooter object

Source code in naff/models/discord/embed.py
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
@classmethod
def converter(cls, ingest: Union[dict, str, "EmbedFooter"]) -> "EmbedFooter":
    """
    A converter to handle users passing raw strings or dictionaries as footers to the Embed object.

    Args:
        ingest: The data to convert

    Returns:
        An EmbedFooter object
    """
    if isinstance(ingest, str):
        return cls(text=ingest)
    else:
        return cls.from_dict(ingest)

EmbedProvider

Bases: DictSerializationMixin

Represents an embed's provider.

Note

Only used by system embeds, not bots

Attributes:

Name Type Description
name Optional[str]

Provider name

url Optional[str]

Provider url

Source code in naff/models/discord/embed.py
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
@define(kw_only=False)
class EmbedProvider(DictSerializationMixin):
    """
    Represents an embed's provider.

    !!! note
        Only used by system embeds, not bots

    Attributes:
        name: Provider name
        url: Provider url

    """

    name: Optional[str] = field(default=None)
    url: Optional[str] = field(default=None)

Embed

Bases: DictSerializationMixin

Represents a discord embed object.

Source code in naff/models/discord/embed.py
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
@define(kw_only=False)
class Embed(DictSerializationMixin):
    """Represents a discord embed object."""

    title: Optional[str] = field(default=None, repr=True)
    """The title of the embed"""
    description: Optional[str] = field(default=None, repr=True)
    """The description of the embed"""
    color: Optional[Union[Color, dict, tuple, list, str, int]] = field(
        default=None, repr=True, metadata=export_converter(process_color)
    )
    """The colour of the embed"""
    url: Optional[str] = field(default=None, validator=v_optional(instance_of(str)), repr=True)
    """The url the embed should direct to when clicked"""
    timestamp: Optional[Timestamp] = field(
        default=None,
        converter=c_optional(timestamp_converter),
        validator=v_optional(instance_of((datetime, float, int))),
        repr=True,
    )
    """Timestamp of embed content"""
    fields: List[EmbedField] = field(factory=list, converter=EmbedField.from_list, repr=True)
    """A list of [fields][naff.models.discord.embed.EmbedField] to go in the embed"""
    author: Optional[EmbedAuthor] = field(default=None, converter=c_optional(EmbedAuthor.from_dict))
    """The author of the embed"""
    thumbnail: Optional[EmbedAttachment] = field(default=None, converter=c_optional(EmbedAttachment.from_dict))
    """The thumbnail of the embed"""
    image: Optional[EmbedAttachment] = field(default=None, converter=c_optional(EmbedAttachment.from_dict))
    """The image of the embed"""
    video: Optional[EmbedAttachment] = field(
        default=None, converter=c_optional(EmbedAttachment.from_dict), metadata=no_export_meta
    )
    """The video of the embed, only used by system embeds"""
    footer: Optional[EmbedFooter] = field(default=None, converter=c_optional(EmbedFooter.converter))
    """The footer of the embed"""
    provider: Optional[EmbedProvider] = field(
        default=None, converter=c_optional(EmbedProvider.from_dict), metadata=no_export_meta
    )
    """The provider of the embed, only used for system embeds"""
    type: EmbedTypes = field(default=EmbedTypes.RICH, converter=c_optional(EmbedTypes), metadata=no_export_meta)

    @title.validator
    def _name_validation(self, attribute: str, value: Any) -> None:
        """Validate the embed title."""
        if value is not None:
            if isinstance(value, str):
                if len(value) > EMBED_MAX_NAME_LENGTH:
                    raise ValueError(f"Title cannot exceed {EMBED_MAX_NAME_LENGTH} characters")
                return
            raise TypeError("Title must be of type String")

    @description.validator
    def _description_validation(self, attribute: str, value: Any) -> None:
        """Validate the description."""
        if value is not None:
            if isinstance(value, str):
                if len(value) > EMBED_MAX_DESC_LENGTH:
                    raise ValueError(f"Description cannot exceed {EMBED_MAX_DESC_LENGTH} characters")
                return
            raise TypeError("Description must be of type String")

    @fields.validator
    def _fields_validation(self, attribute: str, value: Any) -> None:
        """Validate the fields."""
        if isinstance(value, list):
            if len(value) > EMBED_MAX_FIELDS:
                raise ValueError(f"Embeds can only hold {EMBED_MAX_FIELDS} fields")

    def _check_object(self) -> None:
        self._name_validation("title", self.title)
        self._description_validation("description", self.description)
        self._fields_validation("fields", self.fields)

        if len(self) > EMBED_TOTAL_MAX:
            raise ValueError(
                "Your embed is too large, more info at https://discord.com/developers/docs/resources/channel#embed-limits"
            )

    def __len__(self) -> int:
        # yes i know there are far more optimal ways to write this
        # its written like this for readability
        total: int = 0
        if self.title:
            total += len(self.title)
        if self.description:
            total += len(self.description)
        if self.footer:
            total += len(self.footer)
        if self.author:
            total += len(self.author)
        if self.fields:
            total += sum(map(len, self.fields))
        return total

    def __bool__(self) -> bool:
        return any(
            (
                self.title,
                self.description,
                self.fields,
                self.author,
                self.thumbnail,
                self.footer,
                self.image,
                self.video,
            )
        )

    def set_author(
        self,
        name: str,
        url: Optional[str] = None,
        icon_url: Optional[str] = None,
    ) -> None:
        """
        Set the author field of the embed.

        Args:
            name: The text to go in the title section
            url: A url link to the author
            icon_url: A url of an image to use as the icon

        """
        self.author = EmbedAuthor(name=name, url=url, icon_url=icon_url)

    def set_thumbnail(self, url: str) -> None:
        """
        Set the thumbnail of the embed.

        Args:
            url: the url of the image to use

        """
        self.thumbnail = EmbedAttachment(url=url)

    def set_image(self, url: str) -> None:
        """
        Set the image of the embed.

        Args:
            url: the url of the image to use

        """
        self.image = EmbedAttachment(url=url)

    def set_footer(self, text: str, icon_url: Optional[str] = None) -> None:
        """
        Set the footer field of the embed.

        Args:
            text: The text to go in the title section
            icon_url: A url of an image to use as the icon

        """
        self.footer = EmbedFooter(text=text, icon_url=icon_url)

    def add_field(self, name: str, value: Any, inline: bool = False) -> None:
        """
        Add a field to the embed.

        Args:
            name: The title of this field
            value: The value in this field
            inline: Should this field be inline with other fields?

        """
        self.fields.append(EmbedField(name, str(value), inline))
        self._fields_validation("fields", self.fields)

title: Optional[str] = field(default=None, repr=True) class-attribute

The title of the embed

description: Optional[str] = field(default=None, repr=True) class-attribute

The description of the embed

color: Optional[Union[Color, dict, tuple, list, str, int]] = field(default=None, repr=True, metadata=export_converter(process_color)) class-attribute

The colour of the embed

url: Optional[str] = field(default=None, validator=v_optional(instance_of(str)), repr=True) class-attribute

The url the embed should direct to when clicked

timestamp: Optional[Timestamp] = field(default=None, converter=c_optional(timestamp_converter), validator=v_optional(instance_of((datetime, float, int))), repr=True) class-attribute

Timestamp of embed content

fields: List[EmbedField] = field(factory=list, converter=EmbedField.from_list, repr=True) class-attribute

A list of fields to go in the embed

author: Optional[EmbedAuthor] = field(default=None, converter=c_optional(EmbedAuthor.from_dict)) class-attribute

The author of the embed

thumbnail: Optional[EmbedAttachment] = field(default=None, converter=c_optional(EmbedAttachment.from_dict)) class-attribute

The thumbnail of the embed

image: Optional[EmbedAttachment] = field(default=None, converter=c_optional(EmbedAttachment.from_dict)) class-attribute

The image of the embed

video: Optional[EmbedAttachment] = field(default=None, converter=c_optional(EmbedAttachment.from_dict), metadata=no_export_meta) class-attribute

The video of the embed, only used by system embeds

footer: Optional[EmbedFooter] = field(default=None, converter=c_optional(EmbedFooter.converter)) class-attribute

The footer of the embed

provider: Optional[EmbedProvider] = field(default=None, converter=c_optional(EmbedProvider.from_dict), metadata=no_export_meta) class-attribute

The provider of the embed, only used for system embeds

set_author(name, url=None, icon_url=None)

Set the author field of the embed.

Parameters:

Name Type Description Default
name str

The text to go in the title section

required
url Optional[str]

A url link to the author

None
icon_url Optional[str]

A url of an image to use as the icon

None
Source code in naff/models/discord/embed.py
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
def set_author(
    self,
    name: str,
    url: Optional[str] = None,
    icon_url: Optional[str] = None,
) -> None:
    """
    Set the author field of the embed.

    Args:
        name: The text to go in the title section
        url: A url link to the author
        icon_url: A url of an image to use as the icon

    """
    self.author = EmbedAuthor(name=name, url=url, icon_url=icon_url)

set_thumbnail(url)

Set the thumbnail of the embed.

Parameters:

Name Type Description Default
url str

the url of the image to use

required
Source code in naff/models/discord/embed.py
300
301
302
303
304
305
306
307
308
def set_thumbnail(self, url: str) -> None:
    """
    Set the thumbnail of the embed.

    Args:
        url: the url of the image to use

    """
    self.thumbnail = EmbedAttachment(url=url)

set_image(url)

Set the image of the embed.

Parameters:

Name Type Description Default
url str

the url of the image to use

required
Source code in naff/models/discord/embed.py
310
311
312
313
314
315
316
317
318
def set_image(self, url: str) -> None:
    """
    Set the image of the embed.

    Args:
        url: the url of the image to use

    """
    self.image = EmbedAttachment(url=url)

Set the footer field of the embed.

Parameters:

Name Type Description Default
text str

The text to go in the title section

required
icon_url Optional[str]

A url of an image to use as the icon

None
Source code in naff/models/discord/embed.py
320
321
322
323
324
325
326
327
328
329
def set_footer(self, text: str, icon_url: Optional[str] = None) -> None:
    """
    Set the footer field of the embed.

    Args:
        text: The text to go in the title section
        icon_url: A url of an image to use as the icon

    """
    self.footer = EmbedFooter(text=text, icon_url=icon_url)

add_field(name, value, inline=False)

Add a field to the embed.

Parameters:

Name Type Description Default
name str

The title of this field

required
value Any

The value in this field

required
inline bool

Should this field be inline with other fields?

False
Source code in naff/models/discord/embed.py
331
332
333
334
335
336
337
338
339
340
341
342
def add_field(self, name: str, value: Any, inline: bool = False) -> None:
    """
    Add a field to the embed.

    Args:
        name: The title of this field
        value: The value in this field
        inline: Should this field be inline with other fields?

    """
    self.fields.append(EmbedField(name, str(value), inline))
    self._fields_validation("fields", self.fields)

process_embeds(embeds)

Process the passed embeds into a format discord will understand.

Parameters:

Name Type Description Default
embeds Optional[Union[List[Union[Embed, Dict]], Union[Embed, Dict]]]

List of dict / embeds to process

required

Returns:

Type Description
Optional[List[dict]]

formatted list for discord

Source code in naff/models/discord/embed.py
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
def process_embeds(embeds: Optional[Union[List[Union[Embed, Dict]], Union[Embed, Dict]]]) -> Optional[List[dict]]:
    """
    Process the passed embeds into a format discord will understand.

    Args:
        embeds: List of dict / embeds to process

    Returns:
        formatted list for discord

    """
    if embeds is None:
        # Its just empty, so nothing to process.
        return embeds

    if isinstance(embeds, Embed):
        # Single embed, convert it to dict and wrap it into a list for discord.
        return [embeds.to_dict()]

    if isinstance(embeds, dict):
        # We assume the dict correctly represents a single discord embed and just send it blindly
        # after wrapping it in a list for discord
        return [embeds]

    if isinstance(embeds, list):
        # A list of embeds, convert Embed to dict representation if needed.
        return [embed.to_dict() if isinstance(embed, Embed) else embed for embed in embeds]

    raise ValueError(f"Invalid embeds: {embeds}")

Asset

Represents a discord asset.

Attributes:

Name Type Description
BASE str

The cdn address for assets

url str

The URL of this asset

hash Optional[str]

The hash of this asset

Source code in naff/models/discord/asset.py
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
@define(kw_only=False)
class Asset:
    """
    Represents a discord asset.

    Attributes:
        BASE str: The `cdn` address for assets
        url str: The URL of this asset
        hash Optional[str]: The hash of this asset

    """

    BASE = "https://cdn.discordapp.com"

    _client: "Client" = field(metadata=no_export_meta)
    _url: str = field(repr=True)
    hash: Optional[str] = field(repr=True, default=None)

    @classmethod
    def from_path_hash(cls, client: "Client", path: str, asset_hash: str) -> "Asset":
        """
        Create an asset from a path and asset's hash.

        Args:
            client: The NAFF bot instance
            path: The CDN Endpoints for the type of asset.
            asset_hash: The hash representation of the target asset.

        Returns:
            A new Asset object

        """
        url = f"{cls.BASE}/{path.format(asset_hash)}"
        return cls(client=client, url=url, hash=asset_hash)

    @property
    def url(self) -> str:
        """The URL of this asset."""
        ext = ".gif" if self.animated else ".png"
        return f"{self._url}{ext}?size=4096"

    def as_url(self, *, extension: str | None = None, size: int = 4096) -> str:
        """
        Get the url of this asset.

        args:
            extension: The extension to override the assets default with
            size: The size of asset to return

        returns:
            A url for this asset with the given parameters
        """
        if not extension:
            extension = ".gif" if self.animated else ".png"
        elif not extension.startswith("."):
            extension = f".{extension}"

        return f"{self._url}{extension}?size={size}"

    @property
    def animated(self) -> bool:
        """True if this asset is animated."""
        return bool(self.hash) and self.hash.startswith("a_")

    async def fetch(self, extension: Optional[str] = None, size: Optional[int] = None) -> bytes:
        """
        Fetch the asset from the Discord CDN.

        Args:
            extension: File extension based on the target image format
            size: The image size, can be any power of two between 16 and 4096.

        Returns:
            Raw byte array of the file

        Raises:
            ValueError: Incorrect file size if not power of 2 between 16 and 4096

        """
        if not extension:
            extension = ".gif" if self.animated else ".png"

        url = self._url + extension

        if size:
            if not ((size != 0) and (size & (size - 1) == 0)):  # if not power of 2
                raise ValueError("Size should be a power of 2")
            if not 16 <= size <= 4096:
                raise ValueError("Size should be between 16 and 4096")

            url = f"{url}?size={size}"

        return await self._client.http.request_cdn(url, self)

    async def save(
        self, fd: Union[str, bytes, "PathLike", int], extension: Optional[str] = None, size: Optional[int] = None
    ) -> int:
        """
        Save the asset to a local file.

        Args:
            fd: Destination path to save the file to.
            extension: File extension based on the target image format.
            size: The image size, can be any power of two between 16 and 4096.

        Returns:
            Status code result of file write

        """
        content = await self.fetch(extension=extension, size=size)
        with open(fd, "wb") as f:
            return f.write(content)

from_path_hash(client, path, asset_hash) classmethod

Create an asset from a path and asset's hash.

Parameters:

Name Type Description Default
client Client

The NAFF bot instance

required
path str

The CDN Endpoints for the type of asset.

required
asset_hash str

The hash representation of the target asset.

required

Returns:

Type Description
Asset

A new Asset object

Source code in naff/models/discord/asset.py
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
@classmethod
def from_path_hash(cls, client: "Client", path: str, asset_hash: str) -> "Asset":
    """
    Create an asset from a path and asset's hash.

    Args:
        client: The NAFF bot instance
        path: The CDN Endpoints for the type of asset.
        asset_hash: The hash representation of the target asset.

    Returns:
        A new Asset object

    """
    url = f"{cls.BASE}/{path.format(asset_hash)}"
    return cls(client=client, url=url, hash=asset_hash)

url() property

The URL of this asset.

Source code in naff/models/discord/asset.py
49
50
51
52
53
@property
def url(self) -> str:
    """The URL of this asset."""
    ext = ".gif" if self.animated else ".png"
    return f"{self._url}{ext}?size=4096"

as_url(*, extension=None, size=4096)

Get the url of this asset.

Parameters:

Name Type Description Default
extension str | None

The extension to override the assets default with

None
size int

The size of asset to return

4096

Returns:

Type Description
str

A url for this asset with the given parameters

Source code in naff/models/discord/asset.py
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
def as_url(self, *, extension: str | None = None, size: int = 4096) -> str:
    """
    Get the url of this asset.

    args:
        extension: The extension to override the assets default with
        size: The size of asset to return

    returns:
        A url for this asset with the given parameters
    """
    if not extension:
        extension = ".gif" if self.animated else ".png"
    elif not extension.startswith("."):
        extension = f".{extension}"

    return f"{self._url}{extension}?size={size}"

animated() property

True if this asset is animated.

Source code in naff/models/discord/asset.py
73
74
75
76
@property
def animated(self) -> bool:
    """True if this asset is animated."""
    return bool(self.hash) and self.hash.startswith("a_")

fetch(extension=None, size=None) async

Fetch the asset from the Discord CDN.

Parameters:

Name Type Description Default
extension Optional[str]

File extension based on the target image format

None
size Optional[int]

The image size, can be any power of two between 16 and 4096.

None

Returns:

Type Description
bytes

Raw byte array of the file

Raises:

Type Description
ValueError

Incorrect file size if not power of 2 between 16 and 4096

Source code in naff/models/discord/asset.py
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
async def fetch(self, extension: Optional[str] = None, size: Optional[int] = None) -> bytes:
    """
    Fetch the asset from the Discord CDN.

    Args:
        extension: File extension based on the target image format
        size: The image size, can be any power of two between 16 and 4096.

    Returns:
        Raw byte array of the file

    Raises:
        ValueError: Incorrect file size if not power of 2 between 16 and 4096

    """
    if not extension:
        extension = ".gif" if self.animated else ".png"

    url = self._url + extension

    if size:
        if not ((size != 0) and (size & (size - 1) == 0)):  # if not power of 2
            raise ValueError("Size should be a power of 2")
        if not 16 <= size <= 4096:
            raise ValueError("Size should be between 16 and 4096")

        url = f"{url}?size={size}"

    return await self._client.http.request_cdn(url, self)

save(fd, extension=None, size=None) async

Save the asset to a local file.

Parameters:

Name Type Description Default
fd Union[str, bytes, PathLike, int]

Destination path to save the file to.

required
extension Optional[str]

File extension based on the target image format.

None
size Optional[int]

The image size, can be any power of two between 16 and 4096.

None

Returns:

Type Description
int

Status code result of file write

Source code in naff/models/discord/asset.py
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
async def save(
    self, fd: Union[str, bytes, "PathLike", int], extension: Optional[str] = None, size: Optional[int] = None
) -> int:
    """
    Save the asset to a local file.

    Args:
        fd: Destination path to save the file to.
        extension: File extension based on the target image format.
        size: The image size, can be any power of two between 16 and 4096.

    Returns:
        Status code result of file write

    """
    content = await self.fetch(extension=extension, size=size)
    with open(fd, "wb") as f:
        return f.write(content)

Color

Source code in naff/models/discord/color.py
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
@define(init=False)
class Color:
    value: int = field(repr=True)
    """The color value as an integer."""

    def __init__(self, color: COLOR_TYPES | None = None) -> None:
        color = color or (0, 0, 0)
        if isinstance(color, int):
            self.value = color
        elif isinstance(color, (tuple, list)):
            color = tuple(color)
            self.rgb = color
        elif isinstance(color, str):
            if re.match(hex_regex, color):
                self.hex = color
            else:
                self.value = BrandColors[color].value  # todo exception handling for better message
        else:
            raise TypeError

    def __str__(self) -> str:
        return self.hex

    # Helper methods

    @staticmethod
    def clamp(x, min_value=0, max_value=255) -> int:
        """Sanitise a value between a minimum and maximum value"""
        return max(min_value, min(x, max_value))

    # Constructor methods

    @classmethod
    def from_rgb(cls, r: int, g: int, b: int) -> "Color":
        """
        Create a Color object from red, green and blue values.

        Args:
            r: The red value.
            g: The green value.
            b: The blue value.

        Returns:
            A Color object.

        """
        return cls((r, g, b))

    @classmethod
    def from_hex(cls, value: str) -> "Color":
        """
        Create a Color object from a hexadecimal string.

        Args:
            value: The hexadecimal string.

        Returns:
            A Color object.

        """
        instance = cls()
        instance.hex = value
        return instance

    @classmethod
    def from_hsv(cls, h: int, s: int, v: int) -> "Color":
        """
        Create a Color object from a hue, saturation and value.

        Args:
            h: The hue value.
            s: The saturation value.
            v: The value value.

        Returns:
            A Color object.

        """
        instance = cls()
        instance.hsv = h, s, v
        return instance

    @classmethod
    def random(cls) -> "Color":
        """Returns random Color instance"""
        # FFFFFF == 16777215
        return cls(randint(0, 16777215))

    # Properties and setter methods

    def _get_byte(self, n) -> int:
        """
        Get the nth byte of the color value

        Args:
            n: The index of the byte to get.

        Returns:
            The nth byte of the color value.

        """
        return (self.value >> (8 * n)) & 255

    @property
    def r(self) -> int:
        """Red color value"""
        return self._get_byte(2)

    @property
    def g(self) -> int:
        """Green color value"""
        return self._get_byte(1)

    @property
    def b(self) -> int:
        """Blue color value"""
        return self._get_byte(0)

    @property
    def rgb(self) -> tuple[int, int, int]:
        """The red, green, blue color values in a tuple"""
        return self.r, self.g, self.b

    @rgb.setter
    def rgb(self, value: tuple[int, int, int]) -> None:
        """Set the color value from a tuple of (r, g, b) values"""
        # noinspection PyTypeChecker
        r, g, b = (self.clamp(v) for v in value)
        self.value = (r << 16) + (g << 8) + b

    @property
    def rgb_float(self) -> tuple[float, float, float]:
        """The red, green, blue color values in a tuple"""
        # noinspection PyTypeChecker
        return tuple(v / 255 for v in self.rgb)

    @property
    def hex(self) -> str:
        """Hexadecimal representation of color value"""
        r, g, b = self.rgb
        return f"#{r:02x}{g:02x}{b:02x}"

    @hex.setter
    def hex(self, value: str) -> None:
        """Set the color value from a hexadecimal string"""
        value = value.lstrip("#")
        # split hex into 3 parts of 2 digits and convert each to int from base-16 number
        self.rgb = tuple(int(value[i : i + 2], 16) for i in (0, 2, 4))

    @property
    def hsv(self) -> tuple[float, float, float]:
        """The hue, saturation, value color values in a tuple"""
        return colorsys.rgb_to_hsv(*self.rgb_float)

    @hsv.setter
    def hsv(self, value) -> None:
        """Set the color value from a tuple of (h, s, v) values"""
        self.rgb = tuple(round(v * 255) for v in colorsys.hsv_to_rgb(*value))

value: int = field(repr=True) class-attribute

The color value as an integer.

clamp(x, min_value=0, max_value=255) staticmethod

Sanitise a value between a minimum and maximum value

Source code in naff/models/discord/color.py
54
55
56
57
@staticmethod
def clamp(x, min_value=0, max_value=255) -> int:
    """Sanitise a value between a minimum and maximum value"""
    return max(min_value, min(x, max_value))

from_rgb(r, g, b) classmethod

Create a Color object from red, green and blue values.

Parameters:

Name Type Description Default
r int

The red value.

required
g int

The green value.

required
b int

The blue value.

required

Returns:

Type Description
Color

A Color object.

Source code in naff/models/discord/color.py
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
@classmethod
def from_rgb(cls, r: int, g: int, b: int) -> "Color":
    """
    Create a Color object from red, green and blue values.

    Args:
        r: The red value.
        g: The green value.
        b: The blue value.

    Returns:
        A Color object.

    """
    return cls((r, g, b))

from_hex(value) classmethod

Create a Color object from a hexadecimal string.

Parameters:

Name Type Description Default
value str

The hexadecimal string.

required

Returns:

Type Description
Color

A Color object.

Source code in naff/models/discord/color.py
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
@classmethod
def from_hex(cls, value: str) -> "Color":
    """
    Create a Color object from a hexadecimal string.

    Args:
        value: The hexadecimal string.

    Returns:
        A Color object.

    """
    instance = cls()
    instance.hex = value
    return instance

from_hsv(h, s, v) classmethod

Create a Color object from a hue, saturation and value.

Parameters:

Name Type Description Default
h int

The hue value.

required
s int

The saturation value.

required
v int

The value value.

required

Returns:

Type Description
Color

A Color object.

Source code in naff/models/discord/color.py
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
@classmethod
def from_hsv(cls, h: int, s: int, v: int) -> "Color":
    """
    Create a Color object from a hue, saturation and value.

    Args:
        h: The hue value.
        s: The saturation value.
        v: The value value.

    Returns:
        A Color object.

    """
    instance = cls()
    instance.hsv = h, s, v
    return instance

random() classmethod

Returns random Color instance

Source code in naff/models/discord/color.py
111
112
113
114
115
@classmethod
def random(cls) -> "Color":
    """Returns random Color instance"""
    # FFFFFF == 16777215
    return cls(randint(0, 16777215))

r() property

Red color value

Source code in naff/models/discord/color.py
132
133
134
135
@property
def r(self) -> int:
    """Red color value"""
    return self._get_byte(2)

g() property

Green color value

Source code in naff/models/discord/color.py
137
138
139
140
@property
def g(self) -> int:
    """Green color value"""
    return self._get_byte(1)

b() property

Blue color value

Source code in naff/models/discord/color.py
142
143
144
145
@property
def b(self) -> int:
    """Blue color value"""
    return self._get_byte(0)

rgb() property writable

The red, green, blue color values in a tuple

Source code in naff/models/discord/color.py
147
148
149
150
@property
def rgb(self) -> tuple[int, int, int]:
    """The red, green, blue color values in a tuple"""
    return self.r, self.g, self.b

rgb_float() property

The red, green, blue color values in a tuple

Source code in naff/models/discord/color.py
159
160
161
162
163
@property
def rgb_float(self) -> tuple[float, float, float]:
    """The red, green, blue color values in a tuple"""
    # noinspection PyTypeChecker
    return tuple(v / 255 for v in self.rgb)

hex() property writable

Hexadecimal representation of color value

Source code in naff/models/discord/color.py
165
166
167
168
169
@property
def hex(self) -> str:
    """Hexadecimal representation of color value"""
    r, g, b = self.rgb
    return f"#{r:02x}{g:02x}{b:02x}"

hsv() property writable

The hue, saturation, value color values in a tuple

Source code in naff/models/discord/color.py
178
179
180
181
@property
def hsv(self) -> tuple[float, float, float]:
    """The hue, saturation, value color values in a tuple"""
    return colorsys.rgb_to_hsv(*self.rgb_float)

BrandColors

Bases: Color, Enum

A collection of colors complying to the Discord Brand specification.

https://discord.com/branding

Source code in naff/models/discord/color.py
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
class BrandColors(Color, Enum):
    """
    A collection of colors complying to the Discord Brand specification.

    https://discord.com/branding

    """

    BLURPLE = "#5865F2"
    GREEN = "#57F287"
    YELLOW = "#FEE75C"
    FUCHSIA = "#EB459E"
    RED = "#ED4245"
    WHITE = "#FFFFFF"
    BLACK = "#000000"

MaterialColors

Bases: Color, Enum

A collection of material ui colors.

https://www.materialpalette.com/

Source code in naff/models/discord/color.py
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
class MaterialColors(Color, Enum):
    """
    A collection of material ui colors.

    https://www.materialpalette.com/

    """

    RED = "#F44336"
    PINK = "#E91E63"
    LAVENDER = "#EDB9F5"
    PURPLE = "#9C27B0"
    DEEP_PURPLE = "#673AB7"
    INDIGO = "#3F51B5"
    BLUE = "#2196F3"
    LIGHT_BLUE = "#03A9F4"
    CYAN = "#00BCD4"
    TEAL = "#009688"
    GREEN = "#4CAF50"
    LIGHT_GREEN = "#8BC34A"
    LIME = "#CDDC39"
    YELLOW = "#FFEB3B"
    AMBER = "#FFC107"
    ORANGE = "#FF9800"
    DEEP_ORANGE = "#FF5722"
    BROWN = "#795548"
    GREY = "#9E9E9E"
    BLUE_GREY = "#607D8B"

FlatUIColors

Bases: Color, Enum

A collection of flat ui colours.

https://materialui.co/flatuicolors

Source code in naff/models/discord/color.py
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
class FlatUIColors(Color, Enum):
    """
    A collection of flat ui colours.

    https://materialui.co/flatuicolors

    """

    TURQUOISE = "#1ABC9C"
    EMERLAND = "#2ECC71"
    PETERRIVER = "#3498DB"
    AMETHYST = "#9B59B6"
    WETASPHALT = "#34495E"
    GREENSEA = "#16A085"
    NEPHRITIS = "#27AE60"
    BELIZEHOLE = "#2980B9"
    WISTERIA = "#8E44AD"
    MIDNIGHTBLUE = "#2C3E50"
    SUNFLOWER = "#F1C40F"
    CARROT = "#E67E22"
    ALIZARIN = "#E74C3C"
    CLOUDS = "#ECF0F1"
    CONCRETE = "#95A5A6"
    ORANGE = "#F39C12"
    PUMPKIN = "#D35400"
    POMEGRANATE = "#C0392B"
    SILVER = "#BDC3C7"
    ASBESTOS = "#7F8C8D"

RoleColors

Bases: Color, Enum

A collection of the default role colors Discord provides.

Source code in naff/models/discord/color.py
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
class RoleColors(Color, Enum):
    """A collection of the default role colors Discord provides."""

    TEAL = "#1ABC9C"
    DARK_TEAL = "#11806A"
    GREEN = "#2ECC71"
    DARK_GREEN = "#1F8B4C"
    BLUE = "#3498DB"
    DARK_BLUE = "#206694"
    PURPLE = "#9B59B6"
    DARK_PURPLE = "#71368A"
    MAGENTA = "#E91E63"
    DARK_MAGENTA = "#AD1457"
    YELLOW = "#F1C40F"
    DARK_YELLOW = "#C27C0E"
    ORANGE = "#E67E22"
    DARK_ORANGE = "#A84300"
    RED = "#E74C3C"
    DARK_RED = "#992D22"
    LIGHTER_GRAY = "#95A5A6"
    LIGHT_GRAY = "#979C9F"
    DARK_GRAY = "#607D8B"
    DARKER_GRAY = "#546E7A"

    # a certain other lib called the yellows this
    # i honestly cannot decide if they are or not
    # so why not satisfy everyone here?
    GOLD = YELLOW
    DARK_GOLD = DARK_YELLOW

    # aliases
    LIGHT_GREY = LIGHT_GRAY
    LIGHTER_GREY = LIGHTER_GRAY
    DARK_GREY = DARK_GRAY
    DARKER_GREY = DARKER_GRAY

process_color(color)

Process color to a format that can be used by discord.

Parameters:

Name Type Description Default
color Color | dict | COLOR_TYPES | None

The color to process.

required

Returns:

Type Description
int | None

The processed color value.

Source code in naff/models/discord/color.py
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
def process_color(color: Color | dict | COLOR_TYPES | None) -> int | None:
    """
    Process color to a format that can be used by discord.

    Args:
        color: The color to process.

    Returns:
        The processed color value.

    """
    if not color:
        return None
    elif isinstance(color, Color):
        return color.value
    elif isinstance(color, dict):
        return color["value"]
    elif isinstance(color, (tuple, list, str, int)):
        return Color(color).value

    raise ValueError(f"Invalid color: {type(color)}")

File

Representation of a file.

Used for sending files to discord.

Source code in naff/models/discord/file.py
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
@define(kw_only=False)
class File:
    """
    Representation of a file.

    Used for sending files to discord.

    """

    file: Union["IOBase", BinaryIO, "Path", str] = field(repr=True)
    """Location of file to send or the bytes."""
    file_name: Optional[str] = field(repr=True, default=None)
    """Set a filename that will be displayed when uploaded to discord. If you leave this empty, the file will be called `file` by default"""

    def open_file(self) -> BinaryIO:
        """
        Opens the file.

        Returns:
            A file-like BinaryIO object.

        """
        if isinstance(self.file, (IOBase, BinaryIO)):
            return self.file
        else:
            return open(str(self.file), "rb")

file: Union[IOBase, BinaryIO, Path, str] = field(repr=True) class-attribute

Location of file to send or the bytes.

file_name: Optional[str] = field(repr=True, default=None) class-attribute

Set a filename that will be displayed when uploaded to discord. If you leave this empty, the file will be called file by default

open_file()

Opens the file.

Returns:

Type Description
BinaryIO

A file-like BinaryIO object.

Source code in naff/models/discord/file.py
24
25
26
27
28
29
30
31
32
33
34
35
def open_file(self) -> BinaryIO:
    """
    Opens the file.

    Returns:
        A file-like BinaryIO object.

    """
    if isinstance(self.file, (IOBase, BinaryIO)):
        return self.file
    else:
        return open(str(self.file), "rb")

open_file(file)

Opens the file.

Parameters:

Name Type Description Default
file UPLOADABLE_TYPE

The target file or path to file.

required

Returns:

Type Description
BinaryIO

A file-like BinaryIO object.

Source code in naff/models/discord/file.py
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
def open_file(file: UPLOADABLE_TYPE) -> BinaryIO:
    """
    Opens the file.

    Args:
        file: The target file or path to file.

    Returns:
        A file-like BinaryIO object.

    """
    match file:
        case File():
            return file.open_file()
        case IOBase() | BinaryIO():
            return file
        case Path() | str():
            return open(str(file), "rb")
        case _:
            raise ValueError(f"{file} is not a valid file")

Commands

BaseCommand

Bases: DictSerializationMixin, CallbackObject

An object all commands inherit from. Outlines the basic structure of a command, and handles checks.

Attributes:

Name Type Description
extension Any

The extension this command belongs to.

enabled bool

Whether this command is enabled

checks list

Any checks that must be run before this command can be run

callback Callable[..., Coroutine]

The coroutine to be called for this command

error_callback Callable[..., Coroutine]

The coroutine to be called when an error occurs

pre_run_callback Callable[..., Coroutine]

A coroutine to be called before this command is run but after the checks

post_run_callback Callable[..., Coroutine]

A coroutine to be called after this command has run

Source code in naff/models/naff/command.py
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
@define()
class BaseCommand(DictSerializationMixin, CallbackObject):
    """
    An object all commands inherit from. Outlines the basic structure of a command, and handles checks.

    Attributes:
        extension: The extension this command belongs to.
        enabled: Whether this command is enabled
        checks: Any checks that must be run before this command can be run
        callback: The coroutine to be called for this command
        error_callback: The coroutine to be called when an error occurs
        pre_run_callback: A coroutine to be called before this command is run **but** after the checks
        post_run_callback: A coroutine to be called after this command has run

    """

    extension: Any = field(default=None, metadata=docs("The extension this command belongs to") | no_export_meta)

    enabled: bool = field(default=True, metadata=docs("Whether this can be run at all") | no_export_meta)
    checks: list = field(
        factory=list, metadata=docs("Any checks that must be *checked* before the command can run") | no_export_meta
    )
    cooldown: Cooldown = field(
        default=MISSING, metadata=docs("An optional cooldown to apply to the command") | no_export_meta
    )
    max_concurrency: MaxConcurrency = field(
        default=MISSING,
        metadata=docs("An optional maximum number of concurrent instances to apply to the command") | no_export_meta,
    )

    callback: Callable[..., Coroutine] = field(
        default=None, metadata=docs("The coroutine to be called for this command") | no_export_meta
    )
    error_callback: Callable[..., Coroutine] = field(
        default=None, metadata=no_export_meta | docs("The coroutine to be called when an error occurs")
    )
    pre_run_callback: Callable[..., Coroutine] = field(
        default=None,
        metadata=no_export_meta
        | docs("The coroutine to be called before the command is executed, **but** after the checks"),
    )
    post_run_callback: Callable[..., Coroutine] = field(
        default=None, metadata=no_export_meta | docs("The coroutine to be called after the command has executed")
    )

    def __attrs_post_init__(self) -> None:
        if self.callback is not None:
            if hasattr(self.callback, "checks"):
                self.checks += self.callback.checks
            if hasattr(self.callback, "cooldown"):
                self.cooldown = self.callback.cooldown
            if hasattr(self.callback, "max_concurrency"):
                self.max_concurrency = self.callback.max_concurrency

    def __hash__(self) -> int:
        return id(self)

    async def __call__(self, context: "Context", *args, **kwargs) -> None:
        """
        Calls this command.

        Args:
            context: The context of this command
            args: Any
            kwargs: Any

        """
        # signals if a semaphore has been acquired, for exception handling
        # if present assume one will be acquired
        max_conc_acquired = self.max_concurrency is not MISSING

        try:
            if await self._can_run(context):
                if self.pre_run_callback is not None:
                    await self.call_with_binding(self.pre_run_callback, context, *args, **kwargs)

                if self.extension is not None and self.extension.extension_prerun:
                    for prerun in self.extension.extension_prerun:
                        await prerun(context, *args, **kwargs)

                await self.call_callback(self.callback, context)

                if self.post_run_callback is not None:
                    await self.call_with_binding(self.post_run_callback, context, *args, **kwargs)

                if self.extension is not None and self.extension.extension_postrun:
                    for postrun in self.extension.extension_postrun:
                        await postrun(context, *args, **kwargs)

        except Exception as e:
            # if a MaxConcurrencyReached-exception is raised a connection was never acquired
            max_conc_acquired = not isinstance(e, MaxConcurrencyReached)

            if self.error_callback:
                await self.error_callback(e, context, *args, **kwargs)
            elif self.extension and self.extension.extension_error:
                await self.extension.extension_error(e, context, *args, **kwargs)
            else:
                raise
        finally:
            if self.max_concurrency is not MISSING and max_conc_acquired:
                await self.max_concurrency.release(context)

    @staticmethod
    def _get_converter_function(anno: type[Converter] | Converter, name: str) -> Callable[[Context, str], Any]:
        num_params = len(get_parameters(anno.convert))

        # if we have three parameters for the function, it's likely it has a self parameter
        # so we need to get rid of it by initing - typehinting hates this, btw!
        # the below line will error out if we aren't supposed to init it, so that works out
        try:
            actual_anno: Converter = anno() if num_params == 3 else anno  # type: ignore
        except TypeError:
            raise ValueError(
                f"{get_object_name(anno)} for {name} is invalid: converters must have exactly 2 arguments."
            ) from None

        # we can only get to this point while having three params if we successfully inited
        if num_params == 3:
            num_params -= 1

        if num_params != 2:
            raise ValueError(
                f"{get_object_name(anno)} for {name} is invalid: converters must have exactly 2 arguments."
            )

        return actual_anno.convert

    async def try_convert(self, converter: Optional[Callable], context: "Context", value: Any) -> Any:
        if converter is None:
            return value
        return await maybe_coroutine(converter, context, value)

    def param_config(self, annotation: Any, name: str) -> Tuple[Callable, Optional[dict]]:
        # This thing is complicated. NAFF-annotations can either be annotated directly, or they can be annotated with Annotated[str, CMD_*]
        # This helper function handles both cases, and returns a tuple of the converter and its config (if any)
        if annotation is None:
            return None
        if typing.get_origin(annotation) is Annotated and (args := typing.get_args(annotation)):
            for ann in args:
                v = getattr(ann, name, None)
                if v is not None:
                    return (ann, v)
        return (annotation, getattr(annotation, name, None))

    async def call_callback(self, callback: Callable, context: "Context") -> None:
        _call = callback
        if self.has_binding:
            callback = functools.partial(callback, None, None)
        else:
            callback = functools.partial(callback, None)
        parameters = get_parameters(callback)
        args = []
        kwargs = {}
        if len(parameters) == 0:
            # if no params, user only wants context
            return await self.call_with_binding(_call, context)

        c_args = copy.copy(context.args)
        for param in parameters.values():
            if isinstance(param.annotation, Converter):
                # for any future dev looking at this:
                # this checks if the class here has a convert function
                # it does NOT check if the annotation is actually a subclass of Converter
                # this is an intended behavior for Protocols with the runtime_checkable decorator
                convert = functools.partial(
                    self.try_convert, self._get_converter_function(param.annotation, param.name), context
                )
            else:
                convert = functools.partial(self.try_convert, None, context)
            func, config = self.param_config(param.annotation, "_annotation_dat")
            if config:
                # if user has used an naff-annotation, run the annotation, and pass the result to the user
                local = {"context": context, "extension": self.extension, "param": param.name}
                ano_args = [local[c] for c in config["args"]]
                if param.kind != param.POSITIONAL_ONLY:
                    kwargs[param.name] = func(*ano_args)
                else:
                    args.append(func(*ano_args))
                continue
            elif param.name in context.kwargs:
                # if parameter is in kwargs, user obviously wants it, pass it
                if param.kind != param.POSITIONAL_ONLY:
                    kwargs[param.name] = await convert(context.kwargs[param.name])
                else:
                    args.append(await convert(context.kwargs[param.name]))
                if context.kwargs[param.name] in c_args:
                    c_args.remove(context.kwargs[param.name])
            elif param.default is not param.empty:
                kwargs[param.name] = param.default
            else:
                if not str(param).startswith("*"):
                    if param.kind != param.KEYWORD_ONLY:
                        try:
                            args.append(await convert(c_args.pop(0)))
                        except IndexError:
                            raise ValueError(
                                f"{context.invoke_target} expects {len([p for p in parameters.values() if p.default is p.empty]) + len(callback.args)}"
                                f" arguments but received {len(context.args)} instead"
                            ) from None
                    else:
                        raise ValueError(f"Unable to resolve argument: {param.name}")

        if any(kwargs_reg.match(str(param)) for param in parameters.values()):
            # if user has `**kwargs` pass all remaining kwargs
            kwargs = kwargs | {k: v for k, v in context.kwargs.items() if k not in kwargs}
        if any(args_reg.match(str(param)) for param in parameters.values()):
            # user has `*args` pass all remaining args
            args = args + [await convert(c) for c in c_args]
        return await self.call_with_binding(_call, context, *args, **kwargs)

    async def _can_run(self, context: Context) -> bool:
        """
        Determines if this command can be run.

        Args:
            context: The context of the command

        """
        max_conc_acquired = False  # signals if a semaphore has been acquired, for exception handling

        try:
            if not self.enabled:
                return False

            for _c in self.checks:
                if not await _c(context):
                    raise CommandCheckFailure(self, _c, context)

            if self.extension and self.extension.extension_checks:
                for _c in self.extension.extension_checks:
                    if not await _c(context):
                        raise CommandCheckFailure(self, _c, context)

            if self.max_concurrency is not MISSING:
                if not await self.max_concurrency.acquire(context):
                    raise MaxConcurrencyReached(self, self.max_concurrency)

            if self.cooldown is not MISSING:
                if not await self.cooldown.acquire_token(context):
                    raise CommandOnCooldown(self, await self.cooldown.get_cooldown(context))

            return True

        except Exception:
            if max_conc_acquired:
                await self.max_concurrency.release(context)
            raise

    def error(self, call: Callable[..., Coroutine]) -> Callable[..., Coroutine]:
        """A decorator to declare a coroutine as one that will be run upon an error."""
        if not asyncio.iscoroutinefunction(call):
            raise TypeError("Error handler must be coroutine")
        self.error_callback = call
        return call

    def pre_run(self, call: Callable[..., Coroutine]) -> Callable[..., Coroutine]:
        """A decorator to declare a coroutine as one that will be run before the command."""
        if not asyncio.iscoroutinefunction(call):
            raise TypeError("pre_run must be coroutine")
        self.pre_run_callback = call
        return call

    def post_run(self, call: Callable[..., Coroutine]) -> Callable[..., Coroutine]:
        """A decorator to declare a coroutine as one that will be run after the command has."""
        if not asyncio.iscoroutinefunction(call):
            raise TypeError("post_run must be coroutine")
        self.post_run_callback = call
        return call

error(call)

A decorator to declare a coroutine as one that will be run upon an error.

Source code in naff/models/naff/command.py
278
279
280
281
282
283
def error(self, call: Callable[..., Coroutine]) -> Callable[..., Coroutine]:
    """A decorator to declare a coroutine as one that will be run upon an error."""
    if not asyncio.iscoroutinefunction(call):
        raise TypeError("Error handler must be coroutine")
    self.error_callback = call
    return call

pre_run(call)

A decorator to declare a coroutine as one that will be run before the command.

Source code in naff/models/naff/command.py
285
286
287
288
289
290
def pre_run(self, call: Callable[..., Coroutine]) -> Callable[..., Coroutine]:
    """A decorator to declare a coroutine as one that will be run before the command."""
    if not asyncio.iscoroutinefunction(call):
        raise TypeError("pre_run must be coroutine")
    self.pre_run_callback = call
    return call

post_run(call)

A decorator to declare a coroutine as one that will be run after the command has.

Source code in naff/models/naff/command.py
292
293
294
295
296
297
def post_run(self, call: Callable[..., Coroutine]) -> Callable[..., Coroutine]:
    """A decorator to declare a coroutine as one that will be run after the command has."""
    if not asyncio.iscoroutinefunction(call):
        raise TypeError("post_run must be coroutine")
    self.post_run_callback = call
    return call

check(check)

Add a check to a command.

Parameters:

Name Type Description Default
check Callable[[Context], Awaitable[bool]]

A coroutine as a check for this command

required
Source code in naff/models/naff/command.py
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
def check(check: Callable[["Context"], Awaitable[bool]]) -> Callable[[Coroutine], Coroutine]:
    """
    Add a check to a command.

    Args:
        check: A coroutine as a check for this command

    """

    def wrapper(coro: Coroutine) -> Coroutine:
        if isinstance(coro, BaseCommand):
            coro.checks.append(check)
            return coro
        if not hasattr(coro, "checks"):
            coro.checks = []
        coro.checks.append(check)
        return coro

    return wrapper

cooldown(bucket, rate, interval)

Add a cooldown to a command.

Parameters:

Name Type Description Default
bucket Buckets

The bucket used to track cooldowns

required
rate int

How many commands may be ran per interval

required
interval float

How many seconds to wait for a cooldown

required
Source code in naff/models/naff/command.py
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
def cooldown(bucket: Buckets, rate: int, interval: float) -> Callable[[Coroutine], Coroutine]:
    """
    Add a cooldown to a command.

    Args:
        bucket: The bucket used to track cooldowns
        rate: How many commands may be ran per interval
        interval: How many seconds to wait for a cooldown

    """

    def wrapper(coro: Coroutine) -> Coroutine:
        cooldown_obj = Cooldown(bucket, rate, interval)

        coro.cooldown = cooldown_obj

        return coro

    return wrapper

max_concurrency(bucket, concurrent)

Add a maximum number of concurrent instances to the command.

Parameters:

Name Type Description Default
bucket Buckets

The bucket to enforce the maximum within

required
concurrent int

The maximum number of concurrent instances to allow

required
Source code in naff/models/naff/command.py
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
def max_concurrency(bucket: Buckets, concurrent: int) -> Callable[[Coroutine], Coroutine]:
    """
    Add a maximum number of concurrent instances to the command.

    Args:
        bucket: The bucket to enforce the maximum within
        concurrent: The maximum number of concurrent instances to allow

    """

    def wrapper(coro: Coroutine) -> Coroutine:
        max_conc = MaxConcurrency(concurrent, bucket)

        coro.max_concurrency = max_conc

        return coro

    return wrapper

A decorator to declare a coroutine as a prefixed command.

Parameters:

Name Type Description Default
name Optional[str]

The name of the command. Defaults to the name of the coroutine.

None
aliases Optional[list[str]]

The list of aliases the command can be invoked under.

None
help Optional[str]

The long help text for the command. Defaults to the docstring of the coroutine, if there is one.

None
brief Optional[str]

The short help text for the command. Defaults to the first line of the help text, if there is one.

None
usage Optional[str]

A string displaying how the command can be used. If no string is set, it will default to the command's signature. Useful for help commands.

None
enabled bool

Whether this command can be run at all.

True
hidden bool

If True, the default help command (when it is added) does not show this in the help output.

False
ignore_extra bool

If True, ignores extraneous strings passed to a command if all its requirements are met (e.g. ?foo a b c when only expecting a and b). Otherwise, an error is raised.

True
hierarchical_checking bool

If True and if the base of a subcommand, every subcommand underneath it will run this command's checks before its own. Otherwise, only the subcommand's checks are checked.

True
Source code in naff/models/naff/prefixed_commands.py
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
def prefixed_command(
    name: Optional[str] = None,
    *,
    aliases: Optional[list[str]] = None,
    help: Optional[str] = None,
    brief: Optional[str] = None,
    usage: Optional[str] = None,
    enabled: bool = True,
    hidden: bool = False,
    ignore_extra: bool = True,
    hierarchical_checking: bool = True,
) -> Callable[..., PrefixedCommand]:
    """
    A decorator to declare a coroutine as a prefixed command.

    Args:
        name: The name of the command. Defaults to the name of the coroutine.
        aliases: The list of aliases the command can be invoked under.
        help: The long help text for the command. Defaults to the docstring of the coroutine, if there is one.
        brief: The short help text for the command. Defaults to the first line of the help text, if there is one.
        usage: A string displaying how the command can be used. If no string is set, it will default to the command's signature. Useful for help commands.
        enabled: Whether this command can be run at all.
        hidden: If `True`, the default help command (when it is added) does not show this in the help output.
        ignore_extra: If `True`, ignores extraneous strings passed to a command if all its requirements are met (e.g. ?foo a b c when only expecting a and b). Otherwise, an error is raised.
        hierarchical_checking: If `True` and if the base of a subcommand, every subcommand underneath it will run this command's checks before its own. Otherwise, only the subcommand's checks are checked.
    """

    def wrapper(func: Callable) -> PrefixedCommand:
        return PrefixedCommand(  # type: ignore
            callback=func,
            name=name or func.__name__,
            aliases=aliases or [],
            help=help,
            brief=brief,
            usage=usage,  # type: ignore
            enabled=enabled,
            hidden=hidden,
            ignore_extra=ignore_extra,
            hierarchical_checking=hierarchical_checking,
        )

    return wrapper

Application Commands

BaseComponent

Bases: DictSerializationMixin

A base component class.

Warning

This should never be instantiated.

Source code in naff/models/discord/components.py
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
class BaseComponent(DictSerializationMixin):
    """
    A base component class.

    !!! Warning
        This should never be instantiated.

    """

    def __init__(self) -> None:
        raise NotImplementedError

    @classmethod
    def from_dict_factory(cls, data: dict) -> "TYPE_ALL_COMPONENT":
        data.pop("hash", None)  # Zero clue why discord sometimes include a hash attribute...

        component_type = data.pop("type", None)
        component_class = TYPE_COMPONENT_MAPPING.get(component_type, None)
        if not component_class:
            raise TypeError(f"Unsupported component type for {data} ({component_type}), please consult the docs.")

        return component_class.from_dict(data)

InteractiveComponent

Bases: BaseComponent

A base interactive component class.

Warning

This should never be instantiated.

Source code in naff/models/discord/components.py
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
@define(slots=False)
class InteractiveComponent(BaseComponent):
    """
    A base interactive component class.

    !!! Warning
        This should never be instantiated.

    """

    def __eq__(self, other: Any) -> bool:
        if isinstance(other, dict):
            other = BaseComponent.from_dict_factory(other)
            return self.custom_id == other.custom_id and self.type == other.type
        return False

Button

Bases: InteractiveComponent

Represents a discord ui button.

Attributes:

Name Type Description
style optional[ButtonStyles, int]

Buttons come in a variety of styles to convey different types of actions.

label optional[str]

The text that appears on the button, max 80 characters.

emoji optional[Union[PartialEmoji, dict, str]]

The emoji that appears on the button.

custom_id Optional[str]

A developer-defined identifier for the button, max 100 characters.

url Optional[str]

A url for link-style buttons.

disabled bool

Disable the button and make it not interactable, default false.

Source code in naff/models/discord/components.py
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
@define(kw_only=False)
class Button(InteractiveComponent):
    """
    Represents a discord ui button.

    Attributes:
        style optional[ButtonStyles, int]: Buttons come in a variety of styles to convey different types of actions.
        label optional[str]: The text that appears on the button, max 80 characters.
        emoji optional[Union[PartialEmoji, dict, str]]: The emoji that appears on the button.
        custom_id Optional[str]: A developer-defined identifier for the button, max 100 characters.
        url Optional[str]: A url for link-style buttons.
        disabled bool: Disable the button and make it not interactable, default false.

    """

    style: Union[ButtonStyles, int] = field(repr=True)
    label: Optional[str] = field(default=None)
    emoji: Optional[Union["PartialEmoji", dict, str]] = field(
        repr=True, default=None, metadata=export_converter(process_emoji)
    )
    custom_id: Optional[str] = field(repr=True, default=MISSING, validator=str_validator)
    url: Optional[str] = field(repr=True, default=None)
    disabled: bool = field(repr=True, default=False)
    type: Union[ComponentTypes, int] = field(
        repr=True, default=ComponentTypes.BUTTON, init=False, on_setattr=attrs.setters.frozen
    )

    @style.validator
    def _style_validator(self, attribute: str, value: int) -> None:
        if not isinstance(value, ButtonStyles) and value not in ButtonStyles.__members__.values():
            raise ValueError(f'Button style type of "{value}" not recognized, please consult the docs.')

    def __attrs_post_init__(self) -> None:
        if self.style != ButtonStyles.URL:
            # handle adding a custom id to any button that requires a custom id
            if self.custom_id is MISSING:
                self.custom_id = str(uuid.uuid4())

    def _check_object(self) -> None:
        if self.style == ButtonStyles.URL:
            if self.custom_id not in (None, MISSING):
                raise TypeError("A link button cannot have a `custom_id`!")
            if not self.url:
                raise TypeError("A link button must have a `url`!")
        else:
            if self.url:
                raise TypeError("You can't have a URL on a non-link button!")

        if not self.label and not self.emoji:
            raise TypeError("You must have at least a label or emoji on a button.")

SelectOption

Bases: BaseComponent

Represents a select option.

Attributes:

Name Type Description
label str

The label (max 80 characters)

value str

The value of the select, this is whats sent to your bot

description Optional[str]

A description of this option

emoji Optional[Union[PartialEmoji, dict, str]

An emoji to show in this select option

default bool

Is this option selected by default

Source code in naff/models/discord/components.py
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
@define(kw_only=False)
class SelectOption(BaseComponent):
    """
    Represents a select option.

    Attributes:
        label str: The label (max 80 characters)
        value str: The value of the select, this is whats sent to your bot
        description Optional[str]: A description of this option
        emoji Optional[Union[PartialEmoji, dict, str]: An emoji to show in this select option
        default bool: Is this option selected by default

    """

    label: str = field(repr=True, validator=str_validator)
    value: str = field(repr=True, validator=str_validator)
    description: Optional[str] = field(repr=True, default=None)
    emoji: Optional[Union["PartialEmoji", dict, str]] = field(
        repr=True, default=None, metadata=export_converter(process_emoji)
    )
    default: bool = field(repr=True, default=False)

    @label.validator
    def _label_validator(self, attribute: str, value: str) -> None:
        if not value or len(value) > SELECT_MAX_NAME_LENGTH:
            raise ValueError("Label length should be between 1 and 100.")

    @value.validator
    def _value_validator(self, attribute: str, value: str) -> None:
        if not value or len(value) > SELECT_MAX_NAME_LENGTH:
            raise ValueError("Value length should be between 1 and 100.")

    @description.validator
    def _description_validator(self, attribute: str, value: str) -> None:
        if value is not None and len(value) > SELECT_MAX_NAME_LENGTH:
            raise ValueError("Description length must be 100 or lower.")

Select

Bases: InteractiveComponent

Represents a select component.

Attributes:

Name Type Description
options List[dict]

The choices in the select, max 25.

custom_id str

A developer-defined identifier for the button, max 100 characters.

placeholder str

The custom placeholder text to show if nothing is selected, max 100 characters.

min_values Optional[int]

The minimum number of items that must be chosen. (default 1, min 0, max 25)

max_values Optional[int]

The maximum number of items that can be chosen. (default 1, max 25)

disabled bool

Disable the select and make it not intractable, default false.

type Union[ComponentTypes, int]

The action role type number defined by discord. This cannot be modified.

Source code in naff/models/discord/components.py
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
@define(kw_only=False)
class Select(InteractiveComponent):
    """
    Represents a select component.

    Attributes:
        options List[dict]: The choices in the select, max 25.
        custom_id str: A developer-defined identifier for the button, max 100 characters.
        placeholder str: The custom placeholder text to show if nothing is selected, max 100 characters.
        min_values Optional[int]: The minimum number of items that must be chosen. (default 1, min 0, max 25)
        max_values Optional[int]: The maximum number of items that can be chosen. (default 1, max 25)
        disabled bool: Disable the select and make it not intractable, default false.
        type Union[ComponentTypes, int]: The action role type number defined by discord. This cannot be modified.

    """

    options: List[Union[SelectOption, Dict]] = field(repr=True, factory=list)
    custom_id: str = field(repr=True, factory=lambda: str(uuid.uuid4()), validator=str_validator)
    placeholder: str = field(repr=True, default=None)
    min_values: Optional[int] = field(repr=True, default=1)
    max_values: Optional[int] = field(repr=True, default=1)
    disabled: bool = field(repr=True, default=False)
    type: Union[ComponentTypes, int] = field(
        repr=True, default=ComponentTypes.SELECT, init=False, on_setattr=attrs.setters.frozen
    )

    def __len__(self) -> int:
        return len(self.options)

    @placeholder.validator
    def _placeholder_validator(self, attribute: str, value: str) -> None:
        if value is not None and len(value) > SELECT_MAX_NAME_LENGTH:
            raise ValueError("Placeholder length must be 100 or lower.")

    @min_values.validator
    def _min_values_validator(self, attribute: str, value: int) -> None:
        if value < 0:
            raise ValueError("Select min value cannot be a negative number.")

    @max_values.validator
    def _max_values_validator(self, attribute: str, value: int) -> None:
        if value < 0:
            raise ValueError("Select max value cannot be a negative number.")

    @options.validator
    def _options_validator(self, attribute: str, value: List[Union[SelectOption, Dict]]) -> None:
        if not all(isinstance(x, (SelectOption, Dict)) for x in value):
            raise ValueError("Select options must be of type `SelectOption`")

    def _check_object(self) -> None:
        if not self.custom_id:
            raise TypeError("You need to have a custom id to identify the select.")

        if not self.options:
            raise TypeError("Selects needs to have at least 1 option.")

        if len(self.options) > SELECTS_MAX_OPTIONS:
            raise TypeError("Selects can only hold 25 options")

        if self.max_values < self.min_values:
            raise TypeError("Selects max value cannot be less than min value.")

    def add_option(self, option: SelectOption) -> None:
        if not isinstance(option, (SelectOption, Dict)):
            raise ValueError(f"Select option must be of `SelectOption` type, not {type(option)}")
        self.options.append(option)

ActionRow

Bases: BaseComponent

Represents an action row.

Attributes:

Name Type Description
components List[Union[dict, Select, Button]]

The components within this action row

type Union[ComponentTypes, int]

The action role type number defined by discord. This cannot be modified.

Source code in naff/models/discord/components.py
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
@define(kw_only=False)
class ActionRow(BaseComponent):
    """
    Represents an action row.

    Attributes:
        components List[Union[dict, Select, Button]]: The components within this action row
        type Union[ComponentTypes, int]: The action role type number defined by discord. This cannot be modified.

    """

    _max_items = ACTION_ROW_MAX_ITEMS

    components: Sequence[Union[dict, Select, Button]] = field(repr=True, factory=list)
    type: Union[ComponentTypes, int] = field(
        default=ComponentTypes.ACTION_ROW, init=False, on_setattr=attrs.setters.frozen
    )

    def __init__(self, *components: Union[dict, Select, Button]) -> None:
        self.__attrs_init__(components)
        self.components = [self._component_checks(c) for c in self.components]

    def __len__(self) -> int:
        return len(self.components)

    @classmethod
    def from_dict(cls, data) -> "ActionRow":
        return cls(*data["components"])

    def _component_checks(self, component: Union[dict, Select, Button]) -> Union[Select, Button]:
        if isinstance(component, dict):
            component = BaseComponent.from_dict_factory(component)

        if not issubclass(type(component), InteractiveComponent):
            raise TypeError("You can only add select or button to the action row.")

        component._check_object()
        return component

    def _check_object(self) -> None:
        if not (0 < len(self.components) <= ActionRow._max_items):
            raise TypeError(f"Number of components in one row should be between 1 and {ActionRow._max_items}.")

        if any(x.type == ComponentTypes.SELECT for x in self.components) and len(self.components) != 1:
            raise TypeError("Action row must have only one select component and nothing else.")

    def add_components(self, *components: Union[dict, Button, Select]) -> None:
        """
        Add one or more component(s) to this action row.

        Args:
            *components: The components to add

        """
        self.components += [self._component_checks(c) for c in components]

add_components(*components)

Add one or more component(s) to this action row.

Parameters:

Name Type Description Default
*components Union[dict, Button, Select]

The components to add

()
Source code in naff/models/discord/components.py
276
277
278
279
280
281
282
283
284
def add_components(self, *components: Union[dict, Button, Select]) -> None:
    """
    Add one or more component(s) to this action row.

    Args:
        *components: The components to add

    """
    self.components += [self._component_checks(c) for c in components]

process_components(components)

Process the passed components into a format discord will understand.

Parameters:

Name Type Description Default
components Optional[Union[List[List[Union[BaseComponent, Dict]]], List[Union[BaseComponent, Dict]], BaseComponent, Dict]]

List of dict / components to process

required

Returns:

Type Description
List[Dict]

formatted dictionary for discord

Raises:

Type Description
ValueError

Invalid components

Source code in naff/models/discord/components.py
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
def process_components(
    components: Optional[
        Union[List[List[Union[BaseComponent, Dict]]], List[Union[BaseComponent, Dict]], BaseComponent, Dict]
    ]
) -> List[Dict]:
    """
    Process the passed components into a format discord will understand.

    Args:
        components: List of dict / components to process

    Returns:
        formatted dictionary for discord

    Raises:
        ValueError: Invalid components

    """
    if not components:
        # Its just empty, so nothing to process.
        return components

    if isinstance(components, dict):
        # If a naked dictionary is passed, assume the user knows what they're doing and send it blindly
        # after wrapping it in a list for discord
        return [components]

    if issubclass(type(components), BaseComponent):
        # Naked component was passed
        components = [components]

    if isinstance(components, list):
        if all(isinstance(c, dict) for c in components):
            # user has passed a list of dicts, this is the correct format, blindly send it
            return components

        if all(isinstance(c, list) for c in components):
            # list of lists... actionRow-less sending
            return [ActionRow(*row).to_dict() for row in components]

        if all(issubclass(type(c), InteractiveComponent) for c in components):
            # list of naked components
            return [ActionRow(*components).to_dict()]

        if all(isinstance(c, ActionRow) for c in components):
            # we have a list of action rows
            return [action_row.to_dict() for action_row in components]

    raise ValueError(f"Invalid components: {components}")

spread_to_rows(*components, max_in_row=5)

A helper function that spreads your components into ActionRows of a set size.

Parameters:

Name Type Description Default
*components Union[ActionRow, Button, Select]

The components to spread, use None to explicit start a new row

()
max_in_row int

The maximum number of components in each row

5

Returns:

Type Description
List[ActionRow]

List[ActionRow] of components spread to rows

Raises:

Type Description
ValueError

Too many or few components or rows

Source code in naff/models/discord/components.py
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
def spread_to_rows(*components: Union[ActionRow, Button, Select], max_in_row: int = 5) -> List[ActionRow]:
    """
    A helper function that spreads your components into `ActionRow`s of a set size.

    Args:
        *components: The components to spread, use `None` to explicit start a new row
        max_in_row: The maximum number of components in each row

    Returns:
        List[ActionRow] of components spread to rows

    Raises:
        ValueError: Too many or few components or rows

    """
    # todo: incorrect format errors
    if not components or len(components) > 25:
        raise ValueError("Number of components should be between 1 and 25.")
    if not 1 <= max_in_row <= 5:
        raise ValueError("max_in_row should be between 1 and 5.")

    rows = []
    button_row = []
    for component in list(components):
        if component is not None and component.type == ComponentTypes.BUTTON:
            button_row.append(component)

            if len(button_row) == max_in_row:
                rows.append(ActionRow(*button_row))
                button_row = []

            continue

        if button_row:
            rows.append(ActionRow(*button_row))
            button_row = []

        if component is not None:
            if component.type == ComponentTypes.ACTION_ROW:
                rows.append(component)
            elif component.type == ComponentTypes.SELECT:
                rows.append(ActionRow(component))
    if button_row:
        rows.append(ActionRow(*button_row))

    if len(rows) > 5:
        raise ValueError("Number of rows exceeds 5.")

    return rows

get_components_ids(component)

Creates a generator with the custom_id of a component or list of components.

Parameters:

Name Type Description Default
component Union[str, dict, list, InteractiveComponent]

Objects to get custom_ids from

required

Returns:

Type Description
Iterator[str]

Generator with the custom_id of a component or list of components.

Raises:

Type Description
ValueError

Unknown component type

Source code in naff/models/discord/components.py
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
def get_components_ids(component: Union[str, dict, list, InteractiveComponent]) -> Iterator[str]:
    """
    Creates a generator with the `custom_id` of a component or list of components.

    Args:
        component: Objects to get `custom_id`s from

    Returns:
        Generator with the `custom_id` of a component or list of components.

    Raises:
        ValueError: Unknown component type

    """
    if isinstance(component, str):
        yield component
    elif isinstance(component, dict):
        if component["type"] == ComponentTypes.actionrow:
            yield from (comp["custom_id"] for comp in component["components"] if "custom_id" in comp)
        elif "custom_id" in component:
            yield component["custom_id"]
    elif c_id := getattr(component, "custom_id", None):
        yield c_id
    elif isinstance(component, ActionRow):
        yield from (comp_id for comp in component.components for comp_id in get_components_ids(comp))

    elif isinstance(component, list):
        yield from (comp_id for comp in component for comp_id in get_components_ids(comp))
    else:
        raise ValueError(f"Unknown component type of {component} ({type(component)}). " f"Expected str, dict or list")

InputText

Bases: InteractiveComponent

An input component for modals

Source code in naff/models/discord/modal.py
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
@define(kw_only=False)
class InputText(InteractiveComponent):
    """An input component for modals"""

    type: Union[ComponentTypes, int] = field(
        default=ComponentTypes.INPUT_TEXT, init=False, on_setattr=attrs.setters.frozen
    )

    label: str = field(validator=str_validator)
    """the label for this component"""
    style: Union[TextStyles, int] = field()
    """the Text Input Style for single or multiple lines input"""

    custom_id: Optional[str] = field(factory=lambda: str(uuid.uuid4()), validator=str_validator)
    """a developer-defined identifier for the input, max 100 characters"""

    placeholder: Optional[str] = field(default=MISSING, validator=str_validator, kw_only=True)
    """custom placeholder text if the input is empty, max 100 characters"""
    value: Optional[str] = field(default=MISSING, validator=str_validator, kw_only=True)
    """a pre-filled value for this component, max 4000 characters"""

    required: bool = field(default=True, kw_only=True)
    """whether this component is required to be filled, default true"""
    min_length: Optional[int] = field(default=MISSING, kw_only=True)
    """the minimum input length for a text input, min 0, max 4000"""
    max_length: Optional[int] = field(default=MISSING, kw_only=True)
    """the maximum input length for a text input, min 1, max 4000. Must be more than min_length."""

label: str = field(validator=str_validator) class-attribute

the label for this component

style: Union[TextStyles, int] = field() class-attribute

the Text Input Style for single or multiple lines input

custom_id: Optional[str] = field(factory=lambda : str(uuid.uuid4()), validator=str_validator) class-attribute

a developer-defined identifier for the input, max 100 characters

placeholder: Optional[str] = field(default=MISSING, validator=str_validator, kw_only=True) class-attribute

custom placeholder text if the input is empty, max 100 characters

value: Optional[str] = field(default=MISSING, validator=str_validator, kw_only=True) class-attribute

a pre-filled value for this component, max 4000 characters

required: bool = field(default=True, kw_only=True) class-attribute

whether this component is required to be filled, default true

min_length: Optional[int] = field(default=MISSING, kw_only=True) class-attribute

the minimum input length for a text input, min 0, max 4000

max_length: Optional[int] = field(default=MISSING, kw_only=True) class-attribute

the maximum input length for a text input, min 1, max 4000. Must be more than min_length.

ShortText

Bases: InputText

A single line input component for modals

Source code in naff/models/discord/modal.py
50
51
52
53
54
@define(kw_only=False)
class ShortText(InputText):
    """A single line input component for modals"""

    style: Union[TextStyles, int] = field(default=TextStyles.SHORT, kw_only=True)

ParagraphText

Bases: InputText

A multi line input component for modals

Source code in naff/models/discord/modal.py
57
58
59
60
61
@define(kw_only=False)
class ParagraphText(InputText):
    """A multi line input component for modals"""

    style: Union[TextStyles, int] = field(default=TextStyles.PARAGRAPH, kw_only=True)

Modal

Bases: DictSerializationMixin

Form submission style component on discord

Source code in naff/models/discord/modal.py
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
@define(kw_only=False)
class Modal(DictSerializationMixin):
    """Form submission style component on discord"""

    type: Union[CallbackTypes, int] = field(default=CallbackTypes.MODAL, init=False, on_setattr=attrs.setters.frozen)
    title: str = field(validator=str_validator)
    """the title of the popup modal, max 45 characters"""
    components: List[InputText] = field()
    """between 1 and 5 (inclusive) components that make up the modal"""
    custom_id: Optional[str] = field(factory=lambda: str(uuid.uuid4()), validator=str_validator)
    """a developer-defined identifier for the component, max 100 characters"""

    def __attrs_post_init__(self) -> None:
        if self.custom_id is MISSING:
            self.custom_id = str(uuid.uuid4())

    def to_dict(self) -> dict:
        data = super().to_dict()
        components = [{"type": ComponentTypes.ACTION_ROW, "components": [c]} for c in data.get("components", [])]
        return {
            "type": data["type"],
            "data": {"custom_id": data["custom_id"], "title": data["title"], "components": components},
        }

title: str = field(validator=str_validator) class-attribute

the title of the popup modal, max 45 characters

components: List[InputText] = field() class-attribute

between 1 and 5 (inclusive) components that make up the modal

custom_id: Optional[str] = field(factory=lambda : str(uuid.uuid4()), validator=str_validator) class-attribute

a developer-defined identifier for the component, max 100 characters

LocalisedName

Bases: LocalisedField

A localisation object for names.

Source code in naff/models/naff/application_commands.py
78
79
80
81
82
83
@define(field_transformer=attrs_validator(name_validator, skip_fields=["default_locale"]))
class LocalisedName(LocalisedField):
    """A localisation object for names."""

    def __repr__(self) -> str:
        return super().__repr__()

LocalisedDesc

Bases: LocalisedField

A localisation object for descriptions.

Source code in naff/models/naff/application_commands.py
86
87
88
89
90
91
@define(field_transformer=attrs_validator(desc_validator, skip_fields=["default_locale"]))
class LocalisedDesc(LocalisedField):
    """A localisation object for descriptions."""

    def __repr__(self) -> str:
        return super().__repr__()

OptionTypes

Bases: IntEnum

Option types supported by slash commands.

Source code in naff/models/naff/application_commands.py
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
class OptionTypes(IntEnum):
    """Option types supported by slash commands."""

    SUB_COMMAND = 1
    SUB_COMMAND_GROUP = 2
    STRING = 3
    INTEGER = 4
    BOOLEAN = 5
    USER = 6
    CHANNEL = 7
    ROLE = 8
    MENTIONABLE = 9
    NUMBER = 10
    ATTACHMENT = 11

    @classmethod
    def from_type(cls, t: type) -> "OptionTypes":
        """
        Convert data types to their corresponding OptionType.

        Args:
            t: The datatype to convert

        Returns:
            OptionType or None

        """
        if issubclass(t, str):
            return cls.STRING
        if issubclass(t, int):
            return cls.INTEGER
        if issubclass(t, bool):
            return cls.BOOLEAN
        if issubclass(t, BaseUser):
            return cls.USER
        if issubclass(t, channel.BaseChannel):
            return cls.CHANNEL
        if issubclass(t, Role):
            return cls.ROLE
        if issubclass(t, float):
            return cls.NUMBER

from_type(t) classmethod

Convert data types to their corresponding OptionType.

Parameters:

Name Type Description Default
t type

The datatype to convert

required

Returns:

Type Description
OptionTypes

OptionType or None

Source code in naff/models/naff/application_commands.py
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
@classmethod
def from_type(cls, t: type) -> "OptionTypes":
    """
    Convert data types to their corresponding OptionType.

    Args:
        t: The datatype to convert

    Returns:
        OptionType or None

    """
    if issubclass(t, str):
        return cls.STRING
    if issubclass(t, int):
        return cls.INTEGER
    if issubclass(t, bool):
        return cls.BOOLEAN
    if issubclass(t, BaseUser):
        return cls.USER
    if issubclass(t, channel.BaseChannel):
        return cls.CHANNEL
    if issubclass(t, Role):
        return cls.ROLE
    if issubclass(t, float):
        return cls.NUMBER

CallbackTypes

Bases: IntEnum

Types of callback supported by interaction response.

Source code in naff/models/naff/application_commands.py
141
142
143
144
145
146
147
148
149
150
class CallbackTypes(IntEnum):
    """Types of callback supported by interaction response."""

    PONG = 1
    CHANNEL_MESSAGE_WITH_SOURCE = 4
    DEFERRED_CHANNEL_MESSAGE_WITH_SOURCE = 5
    DEFERRED_UPDATE_MESSAGE = 6
    UPDATE_MESSAGE = 7
    AUTOCOMPLETE_RESULT = 8
    MODAL = 9

InteractionCommand

Bases: BaseCommand

Represents a discord abstract interaction command.

Attributes:

Name Type Description
scope

Denotes whether its global or for specific guild.

default_member_permissions Optional[Permissions]

What permissions members need to have by default to use this command.

dm_permission bool

Should this command be available in DMs.

cmd_id Dict[str, Snowflake_Type]

The id of this command given by discord.

callback Callable[..., Coroutine]

The coroutine to callback when this interaction is received.

Source code in naff/models/naff/application_commands.py
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
@define()
class InteractionCommand(BaseCommand):
    """
    Represents a discord abstract interaction command.

    Attributes:
        scope: Denotes whether its global or for specific guild.
        default_member_permissions: What permissions members need to have by default to use this command.
        dm_permission: Should this command be available in DMs.
        cmd_id: The id of this command given by discord.
        callback: The coroutine to callback when this interaction is received.

    """

    name: LocalisedName = field(
        metadata=docs("1-32 character name") | no_export_meta, converter=LocalisedName.converter
    )
    scopes: List["Snowflake_Type"] = field(
        default=[GLOBAL_SCOPE],
        converter=to_snowflake_list,
        metadata=docs("The scopes of this interaction. Global or guild ids") | no_export_meta,
    )
    default_member_permissions: Optional["Permissions"] = field(
        default=None, metadata=docs("What permissions members need to have by default to use this command")
    )
    dm_permission: bool = field(default=True, metadata=docs("Whether this command is enabled in DMs"))
    cmd_id: Dict[str, "Snowflake_Type"] = field(
        factory=dict, metadata=docs("The unique IDs of this commands") | no_export_meta
    )  # scope: cmd_id
    callback: Callable[..., Coroutine] = field(
        default=None, metadata=docs("The coroutine to call when this interaction is received") | no_export_meta
    )
    auto_defer: "AutoDefer" = field(
        default=MISSING,
        metadata=docs("A system to automatically defer this command after a set duration") | no_export_meta,
    )
    nsfw: bool = field(default=False, metadata=docs("This command should only work in NSFW channels"))
    _application_id: "Snowflake_Type" = field(default=None, converter=optional(to_snowflake))

    def __attrs_post_init__(self) -> None:
        if self.callback is not None:
            if hasattr(self.callback, "auto_defer"):
                self.auto_defer = self.callback.auto_defer

        super().__attrs_post_init__()

    def to_dict(self) -> dict:
        data = super().to_dict()

        if self.default_member_permissions is not None:
            data["default_member_permissions"] = str(int(self.default_member_permissions))
        else:
            data["default_member_permissions"] = None

        return data

    def mention(self, scope: Optional["Snowflake_Type"] = None) -> str:
        """
        Returns a string that would mention the interaction.

        Args:
            scope: If the command is available in multiple scope, specify which scope to get the mention for. Defaults to the first available one if not specified.

        Returns:
            The markdown mention.
        """
        if scope:
            cmd_id = self.get_cmd_id(scope=scope)
        else:
            cmd_id = list(self.cmd_id.values())[0]

        return f"</{self.resolved_name}:{cmd_id}>"

    @property
    def resolved_name(self) -> str:
        """A representation of this interaction's name."""
        return str(self.name)

    def get_localised_name(self, locale: str) -> str:
        return self.name.get_locale(locale)

    def get_cmd_id(self, scope: "Snowflake_Type") -> "Snowflake_Type":
        return self.cmd_id.get(scope, self.cmd_id.get(GLOBAL_SCOPE, None))

    @property
    def is_subcommand(self) -> bool:
        return False

    async def _permission_enforcer(self, ctx: "Context") -> bool:
        """A check that enforces Discord permissions."""
        # I wish this wasn't needed, but unfortunately Discord permissions cant be trusted to actually prevent usage
        if self.dm_permission is False:
            return ctx.guild is not None
        return True

mention(scope=None)

Returns a string that would mention the interaction.

Parameters:

Name Type Description Default
scope Optional[Snowflake_Type]

If the command is available in multiple scope, specify which scope to get the mention for. Defaults to the first available one if not specified.

None

Returns:

Type Description
str

The markdown mention.

Source code in naff/models/naff/application_commands.py
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
def mention(self, scope: Optional["Snowflake_Type"] = None) -> str:
    """
    Returns a string that would mention the interaction.

    Args:
        scope: If the command is available in multiple scope, specify which scope to get the mention for. Defaults to the first available one if not specified.

    Returns:
        The markdown mention.
    """
    if scope:
        cmd_id = self.get_cmd_id(scope=scope)
    else:
        cmd_id = list(self.cmd_id.values())[0]

    return f"</{self.resolved_name}:{cmd_id}>"

resolved_name() property

A representation of this interaction's name.

Source code in naff/models/naff/application_commands.py
226
227
228
229
@property
def resolved_name(self) -> str:
    """A representation of this interaction's name."""
    return str(self.name)

ContextMenu

Bases: InteractionCommand

Represents a discord context menu.

Attributes:

Name Type Description
name LocalisedField

The name of this entry.

type CommandTypes

The type of entry (user or message).

Source code in naff/models/naff/application_commands.py
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
@define()
class ContextMenu(InteractionCommand):
    """
    Represents a discord context menu.

    Attributes:
        name: The name of this entry.
        type: The type of entry (user or message).

    """

    name: LocalisedField = field(metadata=docs("1-32 character name"), converter=LocalisedField.converter)
    type: CommandTypes = field(metadata=docs("The type of command, defaults to 1 if not specified"))

    @type.validator
    def _type_validator(self, attribute: str, value: int) -> None:
        if not isinstance(value, CommandTypes):
            if value not in CommandTypes.__members__.values():
                raise ValueError("Context Menu type not recognised, please consult the docs.")
        elif value == CommandTypes.CHAT_INPUT:
            raise ValueError(
                "The CHAT_INPUT type is basically slash commands. Please use the @slash_command() " "decorator instead."
            )

    def to_dict(self) -> dict:
        data = super().to_dict()

        data["name"] = str(self.name)
        return data

SlashCommandChoice

Bases: DictSerializationMixin

Represents a discord slash command choice.

Attributes:

Name Type Description
name LocalisedField

The name the user will see

value Union[str, int, float]

The data sent to your code when this choice is used

Source code in naff/models/naff/application_commands.py
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
@define(kw_only=False)
class SlashCommandChoice(DictSerializationMixin):
    """
    Represents a discord slash command choice.

    Attributes:
        name: The name the user will see
        value: The data sent to your code when this choice is used

    """

    name: LocalisedField = field(converter=LocalisedField.converter)
    value: Union[str, int, float] = field()

    def as_dict(self) -> dict:
        return {"name": str(self.name), "value": self.value, "name_localizations": self.name.to_locale_dict()}

SlashCommandOption

Bases: DictSerializationMixin

Represents a discord slash command option.

Attributes:

Name Type Description
name LocalisedName

The name of this option

type Union[OptionTypes, int]

The type of option

description LocalisedDesc

The description of this option

required bool

"This option must be filled to use the command"

choices List[Union[SlashCommandChoice, Dict]]

A list of choices the user has to pick between

channel_types Optional[list[Union[ChannelTypes, int]]]

The channel types permitted. The option needs to be a channel

min_value Optional[float]

The minimum value permitted. The option needs to be an integer or float

max_value Optional[float]

The maximum value permitted. The option needs to be an integer or float

min_length Optional[int]

The minimum length of text a user can input. The option needs to be a string

max_length Optional[int]

The maximum length of text a user can input. The option needs to be a string

Source code in naff/models/naff/application_commands.py
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
@define(kw_only=False)
class SlashCommandOption(DictSerializationMixin):
    """
    Represents a discord slash command option.

    Attributes:
        name: The name of this option
        type: The type of option
        description: The description of this option
        required: "This option must be filled to use the command"
        choices: A list of choices the user has to pick between
        channel_types: The channel types permitted. The option needs to be a channel
        min_value: The minimum value permitted. The option needs to be an integer or float
        max_value: The maximum value permitted. The option needs to be an integer or float
        min_length: The minimum length of text a user can input. The option needs to be a string
        max_length: The maximum length of text a user can input. The option needs to be a string

    """

    name: LocalisedName = field(converter=LocalisedName.converter)
    type: Union[OptionTypes, int] = field()
    description: LocalisedDesc = field(default="No Description Set", converter=LocalisedDesc.converter)
    required: bool = field(default=True)
    autocomplete: bool = field(default=False)
    choices: List[Union[SlashCommandChoice, Dict]] = field(factory=list)
    channel_types: Optional[list[Union[ChannelTypes, int]]] = field(default=None)
    min_value: Optional[float] = field(default=None)
    max_value: Optional[float] = field(default=None)
    min_length: Optional[int] = field(default=None)
    max_length: Optional[int] = field(default=None)

    @type.validator
    def _type_validator(self, attribute: str, value: int) -> None:
        if value == OptionTypes.SUB_COMMAND or value == OptionTypes.SUB_COMMAND_GROUP:
            raise ValueError(
                "Options cannot be SUB_COMMAND or SUB_COMMAND_GROUP. If you want to use subcommands, "
                "see the @sub_command() decorator."
            )

    @channel_types.validator
    def _channel_types_validator(self, attribute: str, value: Optional[list[OptionTypes]]) -> None:
        if value is not None:
            if self.type != OptionTypes.CHANNEL:
                raise ValueError("The option needs to be CHANNEL to use this")

            allowed_int = [channel_type.value for channel_type in ChannelTypes]
            for item in value:
                if (item not in allowed_int) and (item not in ChannelTypes):
                    raise ValueError(f"{value} is not allowed here")

    @min_value.validator
    def _min_value_validator(self, attribute: str, value: Optional[float]) -> None:
        if value is not None:
            if self.type != OptionTypes.INTEGER and self.type != OptionTypes.NUMBER:
                raise ValueError("`min_value` can only be supplied with int or float options")

            if self.type == OptionTypes.INTEGER:
                if isinstance(value, float):
                    raise ValueError("`min_value` needs to be an int in an int option")

            if self.max_value is not None and self.min_value is not None:
                if self.max_value < self.min_value:
                    raise ValueError("`min_value` needs to be <= than `max_value`")

    @max_value.validator
    def _max_value_validator(self, attribute: str, value: Optional[float]) -> None:
        if value is not None:
            if self.type != OptionTypes.INTEGER and self.type != OptionTypes.NUMBER:
                raise ValueError("`max_value` can only be supplied with int or float options")

            if self.type == OptionTypes.INTEGER:
                if isinstance(value, float):
                    raise ValueError("`max_value` needs to be an int in an int option")

            if self.max_value and self.min_value:
                if self.max_value < self.min_value:
                    raise ValueError("`min_value` needs to be <= than `max_value`")

    @min_length.validator
    def _min_length_validator(self, attribute: str, value: Optional[int]) -> None:
        if value is not None:
            if self.type != OptionTypes.STRING:
                raise ValueError("`min_length` can only be supplied with string options")

            if self.max_length is not None and self.min_length is not None:
                if self.max_length < self.min_length:
                    raise ValueError("`min_length` needs to be <= than `max_length`")

            if self.min_length < 0:
                raise ValueError("`min_length` needs to be >= 0")

    @max_length.validator
    def _max_length_validator(self, attribute: str, value: Optional[int]) -> None:
        if value is not None:
            if self.type != OptionTypes.STRING:
                raise ValueError("`max_length` can only be supplied with string options")

            if self.min_length is not None and self.max_length is not None:
                if self.max_length < self.min_length:
                    raise ValueError("`min_length` needs to be <= than `max_length`")

            if self.max_length < 1:
                raise ValueError("`max_length` needs to be >= 1")

    def as_dict(self) -> dict:
        data = attrs.asdict(self)
        data["name"] = str(self.name)
        data["description"] = str(self.description)
        data["choices"] = [
            choice.as_dict() if isinstance(choice, SlashCommandChoice) else choice for choice in self.choices
        ]
        data["name_localizations"] = self.name.to_locale_dict()
        data["description_localizations"] = self.description.to_locale_dict()

        return data

SlashCommand

Bases: InteractionCommand

Source code in naff/models/naff/application_commands.py
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
@define()
class SlashCommand(InteractionCommand):
    name: LocalisedName = field(converter=LocalisedName.converter)
    description: LocalisedDesc = field(default="No Description Set", converter=LocalisedDesc.converter)

    group_name: LocalisedName = field(default=None, metadata=no_export_meta, converter=LocalisedName.converter)
    group_description: LocalisedDesc = field(
        default="No Description Set", metadata=no_export_meta, converter=LocalisedDesc.converter
    )

    sub_cmd_name: LocalisedName = field(default=None, metadata=no_export_meta, converter=LocalisedName.converter)
    sub_cmd_description: LocalisedDesc = field(
        default="No Description Set", metadata=no_export_meta, converter=LocalisedDesc.converter
    )

    options: List[Union[SlashCommandOption, Dict]] = field(factory=list)
    autocomplete_callbacks: dict = field(factory=dict, metadata=no_export_meta)

    @property
    def resolved_name(self) -> str:
        return (
            f"{self.name}"
            f"{f' {self.group_name}' if bool(self.group_name) else ''}"
            f"{f' {self.sub_cmd_name}' if bool(self.sub_cmd_name) else ''}"
        )

    def get_localised_name(self, locale: str) -> str:
        return (
            f"{self.name.get_locale(locale)}"
            f"{f' {self.group_name.get_locale(locale)}' if bool(self.group_name) else ''}"
            f"{f' {self.sub_cmd_name.get_locale(locale)}' if bool(self.sub_cmd_name) else ''}"
        )

    @property
    def is_subcommand(self) -> bool:
        return bool(self.sub_cmd_name)

    def __attrs_post_init__(self) -> None:
        if self.callback is not None:
            params = get_parameters(self.callback)
            for name, val in params.items():
                annotation = None
                if val.annotation and isinstance(val.annotation, SlashCommandOption):
                    annotation = val.annotation
                elif typing.get_origin(val.annotation) is Annotated:
                    for ann in typing.get_args(val.annotation):
                        if isinstance(ann, SlashCommandOption):
                            annotation = ann

                if annotation:
                    if not self.options:
                        self.options = []
                    annotation.name = name
                    self.options.append(annotation)

            if hasattr(self.callback, "options"):
                if not self.options:
                    self.options = []
                self.options += self.callback.options

        super().__attrs_post_init__()

    def to_dict(self) -> dict:
        data = super().to_dict()

        if self.is_subcommand:
            data["name"] = str(self.sub_cmd_name)
            data["description"] = str(self.sub_cmd_description)
            data["name_localizations"] = self.sub_cmd_name.to_locale_dict()
            data["description_localizations"] = self.sub_cmd_description.to_locale_dict()
            data.pop("default_member_permissions", None)
            data.pop("dm_permission", None)
            data.pop("nsfw", None)
        else:
            data["name_localizations"] = self.name.to_locale_dict()
            data["description_localizations"] = self.description.to_locale_dict()
        return data

    @options.validator
    def options_validator(self, attribute: str, value: List) -> None:
        if value:
            if isinstance(value, list):
                if len(value) > SLASH_CMD_MAX_OPTIONS:
                    raise ValueError(f"Slash commands can only hold {SLASH_CMD_MAX_OPTIONS} options")
                if value != sorted(
                    value,
                    key=lambda x: x.required if isinstance(x, SlashCommandOption) else x["required"],
                    reverse=True,
                ):
                    raise ValueError("Required options must go before optional options")

            else:
                raise TypeError("Options attribute must be either None or a list of options")

    def autocomplete(self, option_name: str) -> Callable[..., Coroutine]:
        """A decorator to declare a coroutine as an option autocomplete."""

        def wrapper(call: Callable[..., Coroutine]) -> Callable[..., Coroutine]:
            if not asyncio.iscoroutinefunction(call):
                raise TypeError("autocomplete must be coroutine")
            self.autocomplete_callbacks[option_name] = call

            # automatically set the option's autocomplete attribute to True
            for opt in self.options:
                if isinstance(opt, dict) and str(opt["name"]) == option_name:
                    opt["autocomplete"] = True
                elif isinstance(opt, SlashCommandOption) and str(opt.name) == option_name:
                    opt.autocomplete = True

            return call

        option_name = option_name.lower()
        return wrapper

    def group(self, name: str = None, description: str = "No Description Set") -> "SlashCommand":

        return SlashCommand(
            name=self.name,
            description=self.description,
            group_name=name,
            group_description=description,
            scopes=self.scopes,
        )

    def subcommand(
        self,
        sub_cmd_name: LocalisedName | str,
        group_name: LocalisedName | str = None,
        sub_cmd_description: Absent[LocalisedDesc | str] = MISSING,
        group_description: Absent[LocalisedDesc | str] = MISSING,
        options: List[Union[SlashCommandOption, Dict]] = None,
        nsfw: bool = False,
    ) -> Callable[..., "SlashCommand"]:
        def wrapper(call: Callable[..., Coroutine]) -> "SlashCommand":
            nonlocal sub_cmd_description

            if not asyncio.iscoroutinefunction(call):
                raise TypeError("Subcommand must be coroutine")

            if sub_cmd_description is MISSING:
                sub_cmd_description = call.__doc__ or "No Description Set"

            return SlashCommand(
                name=self.name,
                description=self.description,
                group_name=group_name or self.group_name,
                group_description=group_description or self.group_description,
                sub_cmd_name=sub_cmd_name,
                sub_cmd_description=sub_cmd_description,
                default_member_permissions=self.default_member_permissions,
                dm_permission=self.dm_permission,
                options=options,
                callback=call,
                scopes=self.scopes,
                nsfw=nsfw,
            )

        return wrapper

autocomplete(option_name)

A decorator to declare a coroutine as an option autocomplete.

Source code in naff/models/naff/application_commands.py
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
def autocomplete(self, option_name: str) -> Callable[..., Coroutine]:
    """A decorator to declare a coroutine as an option autocomplete."""

    def wrapper(call: Callable[..., Coroutine]) -> Callable[..., Coroutine]:
        if not asyncio.iscoroutinefunction(call):
            raise TypeError("autocomplete must be coroutine")
        self.autocomplete_callbacks[option_name] = call

        # automatically set the option's autocomplete attribute to True
        for opt in self.options:
            if isinstance(opt, dict) and str(opt["name"]) == option_name:
                opt["autocomplete"] = True
            elif isinstance(opt, SlashCommandOption) and str(opt.name) == option_name:
                opt.autocomplete = True

        return call

    option_name = option_name.lower()
    return wrapper

slash_command(name, *, description=MISSING, scopes=MISSING, options=None, default_member_permissions=None, dm_permission=True, sub_cmd_name=None, group_name=None, sub_cmd_description='No Description Set', group_description='No Description Set', nsfw=False)

A decorator to declare a coroutine as a slash command.

Note

While the base and group descriptions arent visible in the discord client, currently. We strongly advise defining them anyway, if you're using subcommands, as Discord has said they will be visible in one of the future ui updates.

Parameters:

Name Type Description Default
name str | LocalisedName

1-32 character name of the command

required
description Absent[str | LocalisedDesc]

1-100 character description of the command

MISSING
scopes Absent[List[Snowflake_Type]]

The scope this command exists within

MISSING
options Optional[List[Union[SlashCommandOption, Dict]]]

The parameters for the command, max 25

None
default_member_permissions Optional[Permissions]

What permissions members need to have by default to use this command.

None
dm_permission bool

Should this command be available in DMs.

True
sub_cmd_name str | LocalisedName

1-32 character name of the subcommand

None
sub_cmd_description str | LocalisedDesc

1-100 character description of the subcommand

'No Description Set'
group_name str | LocalisedName

1-32 character name of the group

None
group_description str | LocalisedDesc

1-100 character description of the group

'No Description Set'
nsfw bool

This command should only work in NSFW channels

False

Returns:

Type Description
Callable[[Callable[..., Coroutine]], SlashCommand]

SlashCommand Object

Source code in naff/models/naff/application_commands.py
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
def slash_command(
    name: str | LocalisedName,
    *,
    description: Absent[str | LocalisedDesc] = MISSING,
    scopes: Absent[List["Snowflake_Type"]] = MISSING,
    options: Optional[List[Union[SlashCommandOption, Dict]]] = None,
    default_member_permissions: Optional["Permissions"] = None,
    dm_permission: bool = True,
    sub_cmd_name: str | LocalisedName = None,
    group_name: str | LocalisedName = None,
    sub_cmd_description: str | LocalisedDesc = "No Description Set",
    group_description: str | LocalisedDesc = "No Description Set",
    nsfw: bool = False,
) -> Callable[[Callable[..., Coroutine]], SlashCommand]:
    """
    A decorator to declare a coroutine as a slash command.

    !!! note
        While the base and group descriptions arent visible in the discord client, currently.
        We strongly advise defining them anyway, if you're using subcommands, as Discord has said they will be visible in
        one of the future ui updates.

    Args:
        name: 1-32 character name of the command
        description: 1-100 character description of the command
        scopes: The scope this command exists within
        options: The parameters for the command, max 25
        default_member_permissions: What permissions members need to have by default to use this command.
        dm_permission: Should this command be available in DMs.
        sub_cmd_name: 1-32 character name of the subcommand
        sub_cmd_description: 1-100 character description of the subcommand
        group_name: 1-32 character name of the group
        group_description: 1-100 character description of the group
        nsfw: This command should only work in NSFW channels

    Returns:
        SlashCommand Object

    """

    def wrapper(func: Callable[..., Coroutine]) -> SlashCommand:
        if not asyncio.iscoroutinefunction(func):
            raise ValueError("Commands must be coroutines")

        perm = default_member_permissions
        if hasattr(func, "default_member_permissions"):
            if perm:
                perm = perm | func.default_member_permissions
            else:
                perm = func.default_member_permissions

        _description = description
        if _description is MISSING:
            _description = func.__doc__ if func.__doc__ else "No Description Set"

        cmd = SlashCommand(
            name=name,
            group_name=group_name,
            group_description=group_description,
            sub_cmd_name=sub_cmd_name,
            sub_cmd_description=sub_cmd_description,
            description=_description,
            scopes=scopes if scopes else [GLOBAL_SCOPE],
            default_member_permissions=perm,
            dm_permission=dm_permission,
            callback=func,
            options=options,
            nsfw=nsfw,
        )

        return cmd

    return wrapper

subcommand(base, *, subcommand_group=None, name=None, description=MISSING, base_description=None, base_desc=None, base_default_member_permissions=None, base_dm_permission=True, subcommand_group_description=None, sub_group_desc=None, scopes=None, options=None, nsfw=False)

A decorator specifically tailored for creating subcommands.

Parameters:

Name Type Description Default
base str | LocalisedName

The name of the base command

required
subcommand_group Optional[str | LocalisedName]

The name of the subcommand group, if any.

None
name Optional[str | LocalisedName]

The name of the subcommand, defaults to the name of the coroutine.

None
description Absent[str | LocalisedDesc]

The description of the subcommand

MISSING
base_description Optional[str | LocalisedDesc]

The description of the base command

None
base_desc Optional[str | LocalisedDesc]

An alias of base_description

None
base_default_member_permissions Optional[Permissions]

What permissions members need to have by default to use this command.

None
base_dm_permission bool

Should this command be available in DMs.

True
subcommand_group_description Optional[str | LocalisedDesc]

Description of the subcommand group

None
sub_group_desc Optional[str | LocalisedDesc]

An alias for subcommand_group_description

None
scopes List[Snowflake_Type]

The scopes of which this command is available, defaults to GLOBAL_SCOPE

None
options List[dict]

The options for this command

None
nsfw bool

This command should only work in NSFW channels

False

Returns:

Type Description
Callable[[Coroutine], SlashCommand]

A SlashCommand object

Source code in naff/models/naff/application_commands.py
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
def subcommand(
    base: str | LocalisedName,
    *,
    subcommand_group: Optional[str | LocalisedName] = None,
    name: Optional[str | LocalisedName] = None,
    description: Absent[str | LocalisedDesc] = MISSING,
    base_description: Optional[str | LocalisedDesc] = None,
    base_desc: Optional[str | LocalisedDesc] = None,
    base_default_member_permissions: Optional["Permissions"] = None,
    base_dm_permission: bool = True,
    subcommand_group_description: Optional[str | LocalisedDesc] = None,
    sub_group_desc: Optional[str | LocalisedDesc] = None,
    scopes: List["Snowflake_Type"] = None,
    options: List[dict] = None,
    nsfw: bool = False,
) -> Callable[[Coroutine], SlashCommand]:
    """
    A decorator specifically tailored for creating subcommands.

    Args:
        base: The name of the base command
        subcommand_group: The name of the subcommand group, if any.
        name: The name of the subcommand, defaults to the name of the coroutine.
        description: The description of the subcommand
        base_description: The description of the base command
        base_desc: An alias of `base_description`
        base_default_member_permissions: What permissions members need to have by default to use this command.
        base_dm_permission: Should this command be available in DMs.
        subcommand_group_description: Description of the subcommand group
        sub_group_desc: An alias for `subcommand_group_description`
        scopes: The scopes of which this command is available, defaults to GLOBAL_SCOPE
        options: The options for this command
        nsfw: This command should only work in NSFW channels

    Returns:
        A SlashCommand object

    """

    def wrapper(func) -> SlashCommand:
        if not asyncio.iscoroutinefunction(func):
            raise ValueError("Commands must be coroutines")

        _description = description
        if _description is MISSING:
            _description = func.__doc__ if func.__doc__ else "No Description Set"

        cmd = SlashCommand(
            name=base,
            description=(base_description or base_desc) or "No Description Set",
            group_name=subcommand_group,
            group_description=(subcommand_group_description or sub_group_desc) or "No Description Set",
            sub_cmd_name=name,
            sub_cmd_description=_description,
            default_member_permissions=base_default_member_permissions,
            dm_permission=base_dm_permission,
            scopes=scopes if scopes else [GLOBAL_SCOPE],
            callback=func,
            options=options,
            nsfw=nsfw,
        )
        return cmd

    return wrapper

context_menu(name, context_type, scopes=MISSING, default_member_permissions=None, dm_permission=True)

A decorator to declare a coroutine as a Context Menu.

Parameters:

Name Type Description Default
name str | LocalisedName

1-32 character name of the context menu

required
context_type CommandTypes

The type of context menu

required
scopes Absent[List[Snowflake_Type]]

The scope this command exists within

MISSING
default_member_permissions Optional[Permissions]

What permissions members need to have by default to use this command.

None
dm_permission bool

Should this command be available in DMs.

True

Returns:

Type Description
Callable[[Coroutine], ContextMenu]

ContextMenu object

Source code in naff/models/naff/application_commands.py
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
def context_menu(
    name: str | LocalisedName,
    context_type: "CommandTypes",
    scopes: Absent[List["Snowflake_Type"]] = MISSING,
    default_member_permissions: Optional["Permissions"] = None,
    dm_permission: bool = True,
) -> Callable[[Coroutine], ContextMenu]:
    """
    A decorator to declare a coroutine as a Context Menu.

    Args:
        name: 1-32 character name of the context menu
        context_type: The type of context menu
        scopes: The scope this command exists within
        default_member_permissions: What permissions members need to have by default to use this command.
        dm_permission: Should this command be available in DMs.

    Returns:
        ContextMenu object

    """

    def wrapper(func) -> ContextMenu:
        if not asyncio.iscoroutinefunction(func):
            raise ValueError("Commands must be coroutines")

        perm = default_member_permissions
        if hasattr(func, "default_member_permissions"):
            if perm:
                perm = perm | func.default_member_permissions
            else:
                perm = func.default_member_permissions

        cmd = ContextMenu(
            name=name,
            type=context_type,
            scopes=scopes if scopes else [GLOBAL_SCOPE],
            default_member_permissions=perm,
            dm_permission=dm_permission,
            callback=func,
        )
        return cmd

    return wrapper

component_callback(*custom_id)

Register a coroutine as a component callback.

Component callbacks work the same way as commands, just using components as a way of invoking, instead of messages. Your callback will be given a single argument, ComponentContext

Parameters:

Name Type Description Default
*custom_id str

The custom ID of the component to wait for

()
Source code in naff/models/naff/application_commands.py
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
def component_callback(*custom_id: str) -> Callable[[Coroutine], ComponentCommand]:
    """
    Register a coroutine as a component callback.

    Component callbacks work the same way as commands, just using components as a way of invoking, instead of messages.
    Your callback will be given a single argument, `ComponentContext`

    Args:
        *custom_id: The custom ID of the component to wait for

    """

    def wrapper(func) -> ComponentCommand:
        if not asyncio.iscoroutinefunction(func):
            raise ValueError("Commands must be coroutines")

        return ComponentCommand(name=f"ComponentCallback::{custom_id}", callback=func, listeners=custom_id)

    custom_id = _unpack_helper(custom_id)
    return wrapper

modal_callback(*custom_id)

Register a coroutine as a modal callback.

Modal callbacks work the same way as commands, just using modals as a way of invoking, instead of messages. Your callback will be given a single argument, ModalContext

Parameters:

Name Type Description Default
*custom_id str

The custom ID of the modal to wait for

()
Source code in naff/models/naff/application_commands.py
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
def modal_callback(*custom_id: str) -> Callable[[Coroutine], ModalCommand]:
    """
    Register a coroutine as a modal callback.

    Modal callbacks work the same way as commands, just using modals as a way of invoking, instead of messages.
    Your callback will be given a single argument, `ModalContext`

    Args:
        *custom_id: The custom ID of the modal to wait for
    """

    def wrapper(func) -> ModalCommand:
        if not asyncio.iscoroutinefunction(func):
            raise ValueError("Commands must be coroutines")

        return ModalCommand(name=f"ModalCallback::{custom_id}", callback=func, listeners=custom_id)

    custom_id = _unpack_helper(custom_id)
    return wrapper

slash_option(name, description, opt_type, required=False, autocomplete=False, choices=None, channel_types=None, min_value=None, max_value=None, min_length=None, max_length=None)

A decorator to add an option to a slash command.

Parameters:

Name Type Description Default
name str

1-32 lowercase character name matching ^[\w-]{1,32}$

required
opt_type Union[OptionTypes, int]

The type of option

required
description str

1-100 character description of option

required
required bool

If the parameter is required or optional--default false

False
choices List[Union[SlashCommandChoice, dict]]

A list of choices the user has to pick between (max 25)

None
channel_types Optional[list[Union[ChannelTypes, int]]]

The channel types permitted. The option needs to be a channel

None
min_value Optional[float]

The minimum value permitted. The option needs to be an integer or float

None
max_value Optional[float]

The maximum value permitted. The option needs to be an integer or float

None
min_length Optional[int]

The minimum length of text a user can input. The option needs to be a string

None
max_length Optional[int]

The maximum length of text a user can input. The option needs to be a string

None
Source code in naff/models/naff/application_commands.py
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
def slash_option(
    name: str,
    description: str,
    opt_type: Union[OptionTypes, int],
    required: bool = False,
    autocomplete: bool = False,
    choices: List[Union[SlashCommandChoice, dict]] = None,
    channel_types: Optional[list[Union[ChannelTypes, int]]] = None,
    min_value: Optional[float] = None,
    max_value: Optional[float] = None,
    min_length: Optional[int] = None,
    max_length: Optional[int] = None,
) -> Any:
    r"""
    A decorator to add an option to a slash command.

    Args:
        name: 1-32 lowercase character name matching ^[\w-]{1,32}$
        opt_type: The type of option
        description: 1-100 character description of option
        required: If the parameter is required or optional--default false
        choices: A list of choices the user has to pick between (max 25)
        channel_types: The channel types permitted. The option needs to be a channel
        min_value: The minimum value permitted. The option needs to be an integer or float
        max_value: The maximum value permitted. The option needs to be an integer or float
        min_length: The minimum length of text a user can input. The option needs to be a string
        max_length: The maximum length of text a user can input. The option needs to be a string
    """

    def wrapper(func: Coroutine) -> Coroutine:
        if hasattr(func, "cmd_id"):
            raise Exception("slash_option decorators must be positioned under a slash_command decorator")

        option = SlashCommandOption(
            name=name,
            type=opt_type,
            description=description,
            required=required,
            autocomplete=autocomplete,
            choices=choices if choices else [],
            channel_types=channel_types,
            min_value=min_value,
            max_value=max_value,
            min_length=min_length,
            max_length=max_length,
        )
        if not hasattr(func, "options"):
            func.options = []
        func.options.insert(0, option)
        return func

    return wrapper

slash_default_member_permission(permission)

A decorator to permissions members need to have by default to use a command.

Parameters:

Name Type Description Default
permission Permissions

The permissions to require for to this command

required
Source code in naff/models/naff/application_commands.py
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
def slash_default_member_permission(permission: "Permissions") -> Any:
    """
    A decorator to permissions members need to have by default to use a command.

    Args:
        permission: The permissions to require for to this command

    """

    def wrapper(func: Coroutine) -> Coroutine:
        if hasattr(func, "cmd_id"):
            raise Exception(
                "slash_default_member_permission decorators must be positioned under a slash_command decorator"
            )

        if not hasattr(func, "default_member_permissions") or func.default_member_permissions is None:
            func.default_member_permissions = permission
        else:
            func.default_member_permissions = func.default_member_permissions | permission
        return func

    return wrapper

auto_defer(ephemeral=False, time_until_defer=0.0)

A decorator to add an auto defer to a application command.

Parameters:

Name Type Description Default
ephemeral bool

Should the command be deferred as ephemeral

False
time_until_defer float

How long to wait before deferring automatically

0.0
Source code in naff/models/naff/application_commands.py
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
def auto_defer(ephemeral: bool = False, time_until_defer: float = 0.0) -> Callable[[Coroutine], Coroutine]:
    """
    A decorator to add an auto defer to a application command.

    Args:
        ephemeral: Should the command be deferred as ephemeral
        time_until_defer: How long to wait before deferring automatically

    """

    def wrapper(func: Coroutine) -> Coroutine:
        if hasattr(func, "cmd_id"):
            raise Exception("auto_defer decorators must be positioned under a slash_command decorator")
        func.auto_defer = AutoDefer(enabled=True, ephemeral=ephemeral, time_until_defer=time_until_defer)
        return func

    return wrapper

application_commands_to_dict(commands)

Convert the command list into a format that would be accepted by discord.

Client.interactions should be the variable passed to this

Source code in naff/models/naff/application_commands.py
 938
 939
 940
 941
 942
 943
 944
 945
 946
 947
 948
 949
 950
 951
 952
 953
 954
 955
 956
 957
 958
 959
 960
 961
 962
 963
 964
 965
 966
 967
 968
 969
 970
 971
 972
 973
 974
 975
 976
 977
 978
 979
 980
 981
 982
 983
 984
 985
 986
 987
 988
 989
 990
 991
 992
 993
 994
 995
 996
 997
 998
 999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
def application_commands_to_dict(commands: Dict["Snowflake_Type", Dict[str, InteractionCommand]]) -> dict:
    """
    Convert the command list into a format that would be accepted by discord.

    `Client.interactions` should be the variable passed to this

    """
    cmd_bases = {}  # {cmd_base: [commands]}
    """A store of commands organised by their base command"""
    output = {}
    """The output dictionary"""

    def squash_subcommand(subcommands: List) -> Dict:
        output_data = {}
        groups = {}
        sub_cmds = []
        for subcommand in subcommands:
            if not output_data:
                output_data = {
                    "name": str(subcommand.name),
                    "description": str(subcommand.description),
                    "options": [],
                    "default_member_permissions": str(int(subcommand.default_member_permissions))
                    if subcommand.default_member_permissions
                    else None,
                    "dm_permission": subcommand.dm_permission,
                    "name_localizations": subcommand.name.to_locale_dict(),
                    "description_localizations": subcommand.description.to_locale_dict(),
                    "nsfw": subcommand.nsfw,
                }
            if bool(subcommand.group_name):
                if str(subcommand.group_name) not in groups:
                    groups[str(subcommand.group_name)] = {
                        "name": str(subcommand.group_name),
                        "description": str(subcommand.group_description),
                        "type": int(OptionTypes.SUB_COMMAND_GROUP),
                        "options": [],
                        "name_localizations": subcommand.group_name.to_locale_dict(),
                        "description_localizations": subcommand.group_description.to_locale_dict(),
                    }
                groups[str(subcommand.group_name)]["options"].append(
                    subcommand.to_dict() | {"type": int(OptionTypes.SUB_COMMAND)}
                )
            elif subcommand.is_subcommand:
                sub_cmds.append(subcommand.to_dict() | {"type": int(OptionTypes.SUB_COMMAND)})
        options = list(groups.values()) + sub_cmds
        output_data["options"] = options
        return output_data

    for _scope, cmds in commands.items():
        for cmd in cmds.values():
            cmd_name = str(cmd.name)
            if cmd_name not in cmd_bases:
                cmd_bases[cmd_name] = [cmd]
                continue
            if cmd not in cmd_bases[cmd_name]:
                cmd_bases[cmd_name].append(cmd)

    for cmd_list in cmd_bases.values():
        if any(c.is_subcommand for c in cmd_list):
            # validate all commands share required attributes
            scopes: list[Snowflake_Type] = list({s for c in cmd_list for s in c.scopes})
            base_description = next(
                (
                    c.description
                    for c in cmd_list
                    if str(c.description) is not None and str(c.description) != "No Description Set"
                ),
                "No Description Set",
            )
            nsfw = cmd_list[0].nsfw

            if not all(str(c.description) in (str(base_description), "No Description Set") for c in cmd_list):
                logger.warning(
                    f"Conflicting descriptions found in `{cmd_list[0].name}` subcommands; `{str(base_description)}` will be used"
                )
            if not all(c.default_member_permissions == cmd_list[0].default_member_permissions for c in cmd_list):
                raise ValueError(f"Conflicting `default_member_permissions` values found in `{cmd_list[0].name}`")
            if not all(c.dm_permission == cmd_list[0].dm_permission for c in cmd_list):
                raise ValueError(f"Conflicting `dm_permission` values found in `{cmd_list[0].name}`")
            if not all(c.nsfw == nsfw for c in cmd_list):
                logger.warning(f"Conflicting `nsfw` values found in {cmd_list[0].name} - `True` will be used")
                nsfw = True

            for cmd in cmd_list:
                cmd.scopes = list(scopes)
                cmd.description = base_description
            # end validation of attributes
            cmd_data = squash_subcommand(cmd_list)
        else:
            scopes = cmd_list[0].scopes
            cmd_data = cmd_list[0].to_dict()
        for s in scopes:
            if s not in output:
                output[s] = [cmd_data]
                continue
            output[s].append(cmd_data)
    return output

sync_needed(local_cmd, remote_cmd=None)

Compares a local application command to its remote counterpart to determine if a sync is required.

Parameters:

Name Type Description Default
local_cmd dict

The local json representation of the command

required
remote_cmd Optional[dict]

The json representation of the command from Discord

None

Returns:

Type Description
bool

Boolean indicating if a sync is needed

Source code in naff/models/naff/application_commands.py
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
def sync_needed(local_cmd: dict, remote_cmd: Optional[dict] = None) -> bool:
    """
    Compares a local application command to its remote counterpart to determine if a sync is required.

    Args:
        local_cmd: The local json representation of the command
        remote_cmd: The json representation of the command from Discord

    Returns:
        Boolean indicating if a sync is needed
    """
    if not remote_cmd:
        # No remote version, command must be new
        return True

    if not _compare_commands(local_cmd, remote_cmd):
        # basic comparison of attributes
        return True

    if remote_cmd["type"] == CommandTypes.CHAT_INPUT:
        try:
            if not _compare_options(local_cmd["options"], remote_cmd["options"]):
                # options are not the same, sync needed
                return True
        except KeyError:
            if "options" in local_cmd or "options" in remote_cmd:
                return True

    return False

Context

Resolved

Represents resolved data in an interaction.

Source code in naff/models/naff/context.py
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
@define()
class Resolved:
    """Represents resolved data in an interaction."""

    channels: Dict["Snowflake_Type", "TYPE_MESSAGEABLE_CHANNEL"] = field(
        factory=dict, metadata=docs("A dictionary of channels mentioned in the interaction")
    )
    members: Dict["Snowflake_Type", "Member"] = field(
        factory=dict, metadata=docs("A dictionary of members mentioned in the interaction")
    )
    users: Dict["Snowflake_Type", "User"] = field(
        factory=dict, metadata=docs("A dictionary of users mentioned in the interaction")
    )
    roles: Dict["Snowflake_Type", "Role"] = field(
        factory=dict, metadata=docs("A dictionary of roles mentioned in the interaction")
    )
    messages: Dict["Snowflake_Type", "Message"] = field(
        factory=dict, metadata=docs("A dictionary of messages mentioned in the interaction")
    )
    attachments: Dict["Snowflake_Type", "Attachment"] = field(
        factory=dict, metadata=docs("A dictionary of attachments tied to the interaction")
    )

    @classmethod
    def from_dict(cls, client: "Client", data: dict, guild_id: Optional["Snowflake_Type"] = None) -> "Resolved":
        new_cls = cls()

        if channels := data.get("channels"):
            for key, _channel in channels.items():
                new_cls.channels[key] = client.cache.place_channel_data(_channel)

        if members := data.get("members"):
            for key, _member in members.items():
                new_cls.members[key] = client.cache.place_member_data(
                    guild_id, {**_member, "user": {**data["users"][key]}}
                )

        if users := data.get("users"):
            for key, _user in users.items():
                new_cls.users[key] = client.cache.place_user_data(_user)

        if roles := data.get("roles"):
            for key, _role in roles.items():
                new_cls.roles[key] = client.cache.get_role(to_snowflake(key))

        if messages := data.get("messages"):
            for key, _msg in messages.items():
                new_cls.messages[key] = client.cache.place_message_data(_msg)

        if attachments := data.get("attachments"):
            for key, _attach in attachments.items():
                new_cls.attachments[key] = Attachment.from_dict(_attach, client)

        return new_cls

Context

Represents the context of a command.

Source code in naff/models/naff/context.py
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
@define
class Context:
    """Represents the context of a command."""

    _client: "Client" = field(default=None)
    invoke_target: str = field(default=None, metadata=docs("The name of the command to be invoked"))
    command: Optional["BaseCommand"] = field(default=None, metadata=docs("The command to be invoked"))

    args: List = field(factory=list, metadata=docs("The list of arguments to be passed to the command"))
    kwargs: Dict = field(factory=dict, metadata=docs("The list of keyword arguments to be passed"))

    author: Union["Member", "User"] = field(default=None, metadata=docs("The author of the message"))
    channel: "TYPE_MESSAGEABLE_CHANNEL" = field(default=None, metadata=docs("The channel this was sent within"))
    guild_id: "Snowflake_Type" = field(
        default=None, converter=to_optional_snowflake, metadata=docs("The guild this was sent within, if not a DM")
    )
    message: "Message" = field(default=None, metadata=docs("The message associated with this context"))

    @property
    def guild(self) -> Optional["Guild"]:
        return self._client.cache.get_guild(self.guild_id)

    @property
    def bot(self) -> "Client":
        """A reference to the bot instance."""
        return self._client

    @property
    def voice_state(self) -> Optional["ActiveVoiceState"]:
        return self._client.cache.get_bot_voice_state(self.guild_id)

bot() property

A reference to the bot instance.

Source code in naff/models/naff/context.py
130
131
132
133
@property
def bot(self) -> "Client":
    """A reference to the bot instance."""
    return self._client

InteractionContext

Bases: _BaseInteractionContext, SendMixin

Represents the context of an interaction.

Ephemeral messages:

Ephemeral messages allow you to send messages that only the author of the interaction can see. They are best considered as fire-and-forget, in the sense that you cannot edit them once they have been sent.

Should you attach a component (ie. button) to the ephemeral message, you will be able to edit it when responding to a button interaction.

Source code in naff/models/naff/context.py
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
@define
class InteractionContext(_BaseInteractionContext, SendMixin):
    """
    Represents the context of an interaction.

    !!! info "Ephemeral messages:"
        Ephemeral messages allow you to send messages that only the author of the interaction can see.
        They are best considered as `fire-and-forget`, in the sense that you cannot edit them once they have been sent.

        Should you attach a component (ie. button) to the ephemeral message,
        you will be able to edit it when responding to a button interaction.

    """

    async def defer(self, ephemeral: bool = False) -> None:
        """
        Defers the response, showing a loading state.

        Args:
            ephemeral: Should the response be ephemeral

        """
        if self.deferred or self.responded:
            raise AlreadyDeferred("You have already responded to this interaction!")

        payload = {"type": CallbackTypes.DEFERRED_CHANNEL_MESSAGE_WITH_SOURCE}
        if ephemeral:
            payload["data"] = {"flags": MessageFlags.EPHEMERAL}

        await self._client.http.post_initial_response(payload, self.interaction_id, self._token)
        self.ephemeral = ephemeral
        self.deferred = True

    async def _send_http_request(
        self, message_payload: Union[dict, "FormData"], files: list["UPLOADABLE_TYPE"] | None = None
    ) -> dict:
        if self.responded:
            message_data = await self._client.http.post_followup(
                message_payload, self._client.app.id, self._token, files=files
            )
        else:
            if isinstance(message_payload, FormData) and not self.deferred:
                await self.defer(self.ephemeral)
            if self.deferred:
                message_data = await self._client.http.edit_interaction_message(
                    message_payload, self._client.app.id, self._token, files=files
                )
                self.deferred = False
            else:
                payload = {"type": CallbackTypes.CHANNEL_MESSAGE_WITH_SOURCE, "data": message_payload}
                await self._client.http.post_initial_response(payload, self.interaction_id, self._token, files=files)
                message_data = await self._client.http.get_interaction_message(self._client.app.id, self._token)
            self.responded = True

        return message_data

    async def send(
        self,
        content: Optional[str] = None,
        embeds: Optional[Union[List[Union["Embed", dict]], Union["Embed", dict]]] = None,
        embed: Optional[Union["Embed", dict]] = None,
        components: Optional[
            Union[List[List[Union["BaseComponent", dict]]], List[Union["BaseComponent", dict]], "BaseComponent", dict]
        ] = None,
        stickers: Optional[Union[List[Union["Sticker", "Snowflake_Type"]], "Sticker", "Snowflake_Type"]] = None,
        allowed_mentions: Optional[Union["AllowedMentions", dict]] = None,
        reply_to: Optional[Union["MessageReference", "Message", dict, "Snowflake_Type"]] = None,
        files: Optional[Union[UPLOADABLE_TYPE, List[UPLOADABLE_TYPE]]] = None,
        file: Optional[UPLOADABLE_TYPE] = None,
        tts: bool = False,
        suppress_embeds: bool = False,
        flags: Optional[Union[int, "MessageFlags"]] = None,
        ephemeral: bool = False,
    ) -> "Message":
        """
        Send a message.

        Args:
            content: Message text content.
            embeds: Embedded rich content (up to 6000 characters).
            embed: Embedded rich content (up to 6000 characters).
            components: The components to include with the message.
            stickers: IDs of up to 3 stickers in the server to send in the message.
            allowed_mentions: Allowed mentions for the message.
            reply_to: Message to reference, must be from the same channel.
            files: Files to send, the path, bytes or File() instance, defaults to None. You may have up to 10 files.
            file: Files to send, the path, bytes or File() instance, defaults to None. You may have up to 10 files.
            tts: Should this message use Text To Speech.
            suppress_embeds: Should embeds be suppressed on this send
            flags: Message flags to apply.
            ephemeral bool: Should this message be sent as ephemeral (hidden)

        Returns:
            New message object that was sent.

        """
        if ephemeral:
            flags = MessageFlags.EPHEMERAL
            self.ephemeral = True

        if suppress_embeds:
            if isinstance(flags, int):
                flags = MessageFlags(flags)
            flags = flags | MessageFlags.SUPPRESS_EMBEDS

        return await super().send(
            content,
            embeds=embeds,
            embed=embed,
            components=components,
            stickers=stickers,
            allowed_mentions=allowed_mentions,
            reply_to=reply_to,
            files=files,
            file=file,
            tts=tts,
            flags=flags,
        )

    @property
    def target(self) -> "Absent[Member | User | Message]":
        """For context menus, this will be the object of which was clicked on."""
        thing = MISSING

        match self._context_type:
            # Only searches caches based on what kind of context menu this is

            case CommandTypes.USER:
                # This can only be in the member or user cache
                caches = (
                    (self._client.cache.get_member, (self.guild_id, self.target_id)),
                    (self._client.cache.get_user, self.target_id),
                )
            case CommandTypes.MESSAGE:
                # This can only be in the message cache
                caches = ((self._client.cache.get_message, (self.channel.id, self.target_id)),)
            case _:
                # Most likely a new context type, check all rational caches for the target_id
                logger.warning(f"New Context Type Detected. Please Report: {self._context_type}")
                caches = (
                    (self._client.cache.get_message, (self.channel.id, self.target_id)),
                    (self._client.cache.get_member, (self.guild_id, self.target_id)),
                    (self._client.cache.get_user, self.target_id),
                    (self._client.cache.get_channel, self.target_id),
                    (self._client.cache.get_role, self.target_id),
                    (self._client.cache.get_emoji, self.target_id),  # unlikely, so check last
                )

        for cache, keys in caches:
            thing = cache(*keys)
            if thing is not None:
                break
        return thing

defer(ephemeral=False) async

Defers the response, showing a loading state.

Parameters:

Name Type Description Default
ephemeral bool

Should the response be ephemeral

False
Source code in naff/models/naff/context.py
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
async def defer(self, ephemeral: bool = False) -> None:
    """
    Defers the response, showing a loading state.

    Args:
        ephemeral: Should the response be ephemeral

    """
    if self.deferred or self.responded:
        raise AlreadyDeferred("You have already responded to this interaction!")

    payload = {"type": CallbackTypes.DEFERRED_CHANNEL_MESSAGE_WITH_SOURCE}
    if ephemeral:
        payload["data"] = {"flags": MessageFlags.EPHEMERAL}

    await self._client.http.post_initial_response(payload, self.interaction_id, self._token)
    self.ephemeral = ephemeral
    self.deferred = True

send(content=None, embeds=None, embed=None, components=None, stickers=None, allowed_mentions=None, reply_to=None, files=None, file=None, tts=False, suppress_embeds=False, flags=None, ephemeral=False) async

Send a message.

Parameters:

Name Type Description Default
content Optional[str]

Message text content.

None
embeds Optional[Union[List[Union[Embed, dict]], Union[Embed, dict]]]

Embedded rich content (up to 6000 characters).

None
embed Optional[Union[Embed, dict]]

Embedded rich content (up to 6000 characters).

None
components Optional[Union[List[List[Union[BaseComponent, dict]]], List[Union[BaseComponent, dict]], BaseComponent, dict]]

The components to include with the message.

None
stickers Optional[Union[List[Union[Sticker, Snowflake_Type]], Sticker, Snowflake_Type]]

IDs of up to 3 stickers in the server to send in the message.

None
allowed_mentions Optional[Union[AllowedMentions, dict]]

Allowed mentions for the message.

None
reply_to Optional[Union[MessageReference, Message, dict, Snowflake_Type]]

Message to reference, must be from the same channel.

None
files Optional[Union[UPLOADABLE_TYPE, List[UPLOADABLE_TYPE]]]

Files to send, the path, bytes or File() instance, defaults to None. You may have up to 10 files.

None
file Optional[UPLOADABLE_TYPE]

Files to send, the path, bytes or File() instance, defaults to None. You may have up to 10 files.

None
tts bool

Should this message use Text To Speech.

False
suppress_embeds bool

Should embeds be suppressed on this send

False
flags Optional[Union[int, MessageFlags]]

Message flags to apply.

None
ephemeral bool

Should this message be sent as ephemeral (hidden)

False

Returns:

Type Description
Message

New message object that was sent.

Source code in naff/models/naff/context.py
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
async def send(
    self,
    content: Optional[str] = None,
    embeds: Optional[Union[List[Union["Embed", dict]], Union["Embed", dict]]] = None,
    embed: Optional[Union["Embed", dict]] = None,
    components: Optional[
        Union[List[List[Union["BaseComponent", dict]]], List[Union["BaseComponent", dict]], "BaseComponent", dict]
    ] = None,
    stickers: Optional[Union[List[Union["Sticker", "Snowflake_Type"]], "Sticker", "Snowflake_Type"]] = None,
    allowed_mentions: Optional[Union["AllowedMentions", dict]] = None,
    reply_to: Optional[Union["MessageReference", "Message", dict, "Snowflake_Type"]] = None,
    files: Optional[Union[UPLOADABLE_TYPE, List[UPLOADABLE_TYPE]]] = None,
    file: Optional[UPLOADABLE_TYPE] = None,
    tts: bool = False,
    suppress_embeds: bool = False,
    flags: Optional[Union[int, "MessageFlags"]] = None,
    ephemeral: bool = False,
) -> "Message":
    """
    Send a message.

    Args:
        content: Message text content.
        embeds: Embedded rich content (up to 6000 characters).
        embed: Embedded rich content (up to 6000 characters).
        components: The components to include with the message.
        stickers: IDs of up to 3 stickers in the server to send in the message.
        allowed_mentions: Allowed mentions for the message.
        reply_to: Message to reference, must be from the same channel.
        files: Files to send, the path, bytes or File() instance, defaults to None. You may have up to 10 files.
        file: Files to send, the path, bytes or File() instance, defaults to None. You may have up to 10 files.
        tts: Should this message use Text To Speech.
        suppress_embeds: Should embeds be suppressed on this send
        flags: Message flags to apply.
        ephemeral bool: Should this message be sent as ephemeral (hidden)

    Returns:
        New message object that was sent.

    """
    if ephemeral:
        flags = MessageFlags.EPHEMERAL
        self.ephemeral = True

    if suppress_embeds:
        if isinstance(flags, int):
            flags = MessageFlags(flags)
        flags = flags | MessageFlags.SUPPRESS_EMBEDS

    return await super().send(
        content,
        embeds=embeds,
        embed=embed,
        components=components,
        stickers=stickers,
        allowed_mentions=allowed_mentions,
        reply_to=reply_to,
        files=files,
        file=file,
        tts=tts,
        flags=flags,
    )

target() property

For context menus, this will be the object of which was clicked on.

Source code in naff/models/naff/context.py
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
@property
def target(self) -> "Absent[Member | User | Message]":
    """For context menus, this will be the object of which was clicked on."""
    thing = MISSING

    match self._context_type:
        # Only searches caches based on what kind of context menu this is

        case CommandTypes.USER:
            # This can only be in the member or user cache
            caches = (
                (self._client.cache.get_member, (self.guild_id, self.target_id)),
                (self._client.cache.get_user, self.target_id),
            )
        case CommandTypes.MESSAGE:
            # This can only be in the message cache
            caches = ((self._client.cache.get_message, (self.channel.id, self.target_id)),)
        case _:
            # Most likely a new context type, check all rational caches for the target_id
            logger.warning(f"New Context Type Detected. Please Report: {self._context_type}")
            caches = (
                (self._client.cache.get_message, (self.channel.id, self.target_id)),
                (self._client.cache.get_member, (self.guild_id, self.target_id)),
                (self._client.cache.get_user, self.target_id),
                (self._client.cache.get_channel, self.target_id),
                (self._client.cache.get_role, self.target_id),
                (self._client.cache.get_emoji, self.target_id),  # unlikely, so check last
            )

    for cache, keys in caches:
        thing = cache(*keys)
        if thing is not None:
            break
    return thing

ComponentContext

Bases: InteractionContext

Source code in naff/models/naff/context.py
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
@define
class ComponentContext(InteractionContext):
    custom_id: str = field(default="", metadata=docs("The ID given to the component that has been pressed"))
    component_type: int = field(default=0, metadata=docs("The type of component that has been pressed"))

    values: List = field(factory=list, metadata=docs("The values set"))

    defer_edit_origin: bool = field(default=False, metadata=docs("Are we editing the message the component is on"))

    @classmethod
    def from_dict(cls, data: Dict, client: "Client") -> "ComponentContext":
        """Create a context object from a dictionary."""
        new_cls = super().from_dict(data, client)
        new_cls.token = data["token"]
        new_cls.interaction_id = data["id"]
        new_cls.custom_id = data["data"]["custom_id"]
        new_cls.component_type = data["data"]["component_type"]
        new_cls.message = client.cache.place_message_data(data["message"])
        new_cls.values = data["data"].get("values", [])

        return new_cls

    async def defer(self, ephemeral: bool = False, edit_origin: bool = False) -> None:
        """
        Defers the response, showing a loading state.

        Args:
            ephemeral: Should the response be ephemeral
            edit_origin: Whether we intend to edit the original message

        """
        if self.deferred or self.responded:
            raise AlreadyDeferred("You have already responded to this interaction!")

        payload = {
            "type": CallbackTypes.DEFERRED_UPDATE_MESSAGE
            if edit_origin
            else CallbackTypes.DEFERRED_CHANNEL_MESSAGE_WITH_SOURCE
        }

        if ephemeral:
            if edit_origin:
                raise ValueError("`edit_origin` and `ephemeral` are mutually exclusive")
            payload["data"] = {"flags": MessageFlags.EPHEMERAL}

        await self._client.http.post_initial_response(payload, self.interaction_id, self._token)
        self.deferred = True
        self.ephemeral = ephemeral
        self.defer_edit_origin = edit_origin

    async def edit_origin(
        self,
        content: str = None,
        embeds: Optional[Union[List[Union["Embed", dict]], Union["Embed", dict]]] = None,
        embed: Optional[Union["Embed", dict]] = None,
        components: Optional[
            Union[List[List[Union["BaseComponent", dict]]], List[Union["BaseComponent", dict]], "BaseComponent", dict]
        ] = None,
        allowed_mentions: Optional[Union["AllowedMentions", dict]] = None,
        files: Optional[Union[UPLOADABLE_TYPE, List[UPLOADABLE_TYPE]]] = None,
        file: Optional[UPLOADABLE_TYPE] = None,
        tts: bool = False,
    ) -> "Message":
        """
        Edits the original message of the component.

        Args:
            content: Message text content.
            embeds: Embedded rich content (up to 6000 characters).
            embed: Embedded rich content (up to 6000 characters).
            components: The components to include with the message.
            allowed_mentions: Allowed mentions for the message.
            files: Files to send, the path, bytes or File() instance, defaults to None. You may have up to 10 files.
            file: Files to send, the path, bytes or File() instance, defaults to None. You may have up to 10 files.
            tts: Should this message use Text To Speech.

        Returns:
            The message after it was edited.

        """
        if not self.responded and not self.deferred and (files or file):
            # Discord doesn't allow files at initial response, so we defer then edit.
            await self.defer(edit_origin=True)

        message_payload = message.process_message_payload(
            content=content,
            embeds=embeds or embed,
            components=components,
            allowed_mentions=allowed_mentions,
            tts=tts,
        )

        message_data = None
        if self.deferred:
            if not self.defer_edit_origin:
                logger.warning(
                    "If you want to edit the original message, and need to defer, you must set the `edit_origin` kwarg to True!"
                )

            message_data = await self._client.http.edit_interaction_message(
                message_payload, self._client.app.id, self._token
            )
            self.deferred = False
            self.defer_edit_origin = False
        else:
            payload = {"type": CallbackTypes.UPDATE_MESSAGE, "data": message_payload}
            await self._client.http.post_initial_response(
                payload, self.interaction_id, self._token, files=files or file
            )
            message_data = await self._client.http.get_interaction_message(self._client.app.id, self._token)

        if message_data:
            self.message = self._client.cache.place_message_data(message_data)
            return self.message

from_dict(data, client) classmethod

Create a context object from a dictionary.

Source code in naff/models/naff/context.py
453
454
455
456
457
458
459
460
461
462
463
464
@classmethod
def from_dict(cls, data: Dict, client: "Client") -> "ComponentContext":
    """Create a context object from a dictionary."""
    new_cls = super().from_dict(data, client)
    new_cls.token = data["token"]
    new_cls.interaction_id = data["id"]
    new_cls.custom_id = data["data"]["custom_id"]
    new_cls.component_type = data["data"]["component_type"]
    new_cls.message = client.cache.place_message_data(data["message"])
    new_cls.values = data["data"].get("values", [])

    return new_cls

defer(ephemeral=False, edit_origin=False) async

Defers the response, showing a loading state.

Parameters:

Name Type Description Default
ephemeral bool

Should the response be ephemeral

False
edit_origin bool

Whether we intend to edit the original message

False
Source code in naff/models/naff/context.py
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
async def defer(self, ephemeral: bool = False, edit_origin: bool = False) -> None:
    """
    Defers the response, showing a loading state.

    Args:
        ephemeral: Should the response be ephemeral
        edit_origin: Whether we intend to edit the original message

    """
    if self.deferred or self.responded:
        raise AlreadyDeferred("You have already responded to this interaction!")

    payload = {
        "type": CallbackTypes.DEFERRED_UPDATE_MESSAGE
        if edit_origin
        else CallbackTypes.DEFERRED_CHANNEL_MESSAGE_WITH_SOURCE
    }

    if ephemeral:
        if edit_origin:
            raise ValueError("`edit_origin` and `ephemeral` are mutually exclusive")
        payload["data"] = {"flags": MessageFlags.EPHEMERAL}

    await self._client.http.post_initial_response(payload, self.interaction_id, self._token)
    self.deferred = True
    self.ephemeral = ephemeral
    self.defer_edit_origin = edit_origin

edit_origin(content=None, embeds=None, embed=None, components=None, allowed_mentions=None, files=None, file=None, tts=False) async

Edits the original message of the component.

Parameters:

Name Type Description Default
content str

Message text content.

None
embeds Optional[Union[List[Union[Embed, dict]], Union[Embed, dict]]]

Embedded rich content (up to 6000 characters).

None
embed Optional[Union[Embed, dict]]

Embedded rich content (up to 6000 characters).

None
components Optional[Union[List[List[Union[BaseComponent, dict]]], List[Union[BaseComponent, dict]], BaseComponent, dict]]

The components to include with the message.

None
allowed_mentions Optional[Union[AllowedMentions, dict]]

Allowed mentions for the message.

None
files Optional[Union[UPLOADABLE_TYPE, List[UPLOADABLE_TYPE]]]

Files to send, the path, bytes or File() instance, defaults to None. You may have up to 10 files.

None
file Optional[UPLOADABLE_TYPE]

Files to send, the path, bytes or File() instance, defaults to None. You may have up to 10 files.

None
tts bool

Should this message use Text To Speech.

False

Returns:

Type Description
Message

The message after it was edited.

Source code in naff/models/naff/context.py
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
async def edit_origin(
    self,
    content: str = None,
    embeds: Optional[Union[List[Union["Embed", dict]], Union["Embed", dict]]] = None,
    embed: Optional[Union["Embed", dict]] = None,
    components: Optional[
        Union[List[List[Union["BaseComponent", dict]]], List[Union["BaseComponent", dict]], "BaseComponent", dict]
    ] = None,
    allowed_mentions: Optional[Union["AllowedMentions", dict]] = None,
    files: Optional[Union[UPLOADABLE_TYPE, List[UPLOADABLE_TYPE]]] = None,
    file: Optional[UPLOADABLE_TYPE] = None,
    tts: bool = False,
) -> "Message":
    """
    Edits the original message of the component.

    Args:
        content: Message text content.
        embeds: Embedded rich content (up to 6000 characters).
        embed: Embedded rich content (up to 6000 characters).
        components: The components to include with the message.
        allowed_mentions: Allowed mentions for the message.
        files: Files to send, the path, bytes or File() instance, defaults to None. You may have up to 10 files.
        file: Files to send, the path, bytes or File() instance, defaults to None. You may have up to 10 files.
        tts: Should this message use Text To Speech.

    Returns:
        The message after it was edited.

    """
    if not self.responded and not self.deferred and (files or file):
        # Discord doesn't allow files at initial response, so we defer then edit.
        await self.defer(edit_origin=True)

    message_payload = message.process_message_payload(
        content=content,
        embeds=embeds or embed,
        components=components,
        allowed_mentions=allowed_mentions,
        tts=tts,
    )

    message_data = None
    if self.deferred:
        if not self.defer_edit_origin:
            logger.warning(
                "If you want to edit the original message, and need to defer, you must set the `edit_origin` kwarg to True!"
            )

        message_data = await self._client.http.edit_interaction_message(
            message_payload, self._client.app.id, self._token
        )
        self.deferred = False
        self.defer_edit_origin = False
    else:
        payload = {"type": CallbackTypes.UPDATE_MESSAGE, "data": message_payload}
        await self._client.http.post_initial_response(
            payload, self.interaction_id, self._token, files=files or file
        )
        message_data = await self._client.http.get_interaction_message(self._client.app.id, self._token)

    if message_data:
        self.message = self._client.cache.place_message_data(message_data)
        return self.message

AutocompleteContext

Bases: _BaseInteractionContext

Source code in naff/models/naff/context.py
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
@define
class AutocompleteContext(_BaseInteractionContext):
    focussed_option: str = field(default=MISSING, metadata=docs("The option the user is currently filling in"))

    @classmethod
    def from_dict(cls, data: Dict, client: "Client") -> "ComponentContext":
        """Create a context object from a dictionary."""
        new_cls = super().from_dict(data, client)

        return new_cls

    @property
    def input_text(self) -> str:
        """The text the user has entered so far."""
        return self.kwargs.get(self.focussed_option, "")

    async def send(self, choices: List[Union[str, int, float, Dict[str, Union[str, int, float]]]]) -> None:
        """
        Send your autocomplete choices to discord. Choices must be either a list of strings, or a dictionary following the following format:

        ```json
            {
              "name": str,
              "value": str
            }
        ```
        Where name is the text visible in Discord, and value is the data sent back to your client when that choice is
        chosen.

        Args:
            choices: 25 choices the user can pick

        """
        processed_choices = []
        for choice in choices:
            if isinstance(choice, (int, float)):
                processed_choices.append({"name": str(choice), "value": choice})
            elif isinstance(choice, dict):
                processed_choices.append(choice)
            else:
                choice = str(choice)
                processed_choices.append({"name": choice, "value": choice.replace(" ", "_")})

        payload = {"type": CallbackTypes.AUTOCOMPLETE_RESULT, "data": {"choices": processed_choices}}
        await self._client.http.post_initial_response(payload, self.interaction_id, self._token)

from_dict(data, client) classmethod

Create a context object from a dictionary.

Source code in naff/models/naff/context.py
564
565
566
567
568
569
@classmethod
def from_dict(cls, data: Dict, client: "Client") -> "ComponentContext":
    """Create a context object from a dictionary."""
    new_cls = super().from_dict(data, client)

    return new_cls

input_text() property

The text the user has entered so far.

Source code in naff/models/naff/context.py
571
572
573
574
@property
def input_text(self) -> str:
    """The text the user has entered so far."""
    return self.kwargs.get(self.focussed_option, "")

send(choices) async

Send your autocomplete choices to discord. Choices must be either a list of strings, or a dictionary following the following format:

1
2
3
4
    {
      "name": str,
      "value": str
    }
Where name is the text visible in Discord, and value is the data sent back to your client when that choice is chosen.

Parameters:

Name Type Description Default
choices List[Union[str, int, float, Dict[str, Union[str, int, float]]]]

25 choices the user can pick

required
Source code in naff/models/naff/context.py
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
async def send(self, choices: List[Union[str, int, float, Dict[str, Union[str, int, float]]]]) -> None:
    """
    Send your autocomplete choices to discord. Choices must be either a list of strings, or a dictionary following the following format:

    ```json
        {
          "name": str,
          "value": str
        }
    ```
    Where name is the text visible in Discord, and value is the data sent back to your client when that choice is
    chosen.

    Args:
        choices: 25 choices the user can pick

    """
    processed_choices = []
    for choice in choices:
        if isinstance(choice, (int, float)):
            processed_choices.append({"name": str(choice), "value": choice})
        elif isinstance(choice, dict):
            processed_choices.append(choice)
        else:
            choice = str(choice)
            processed_choices.append({"name": choice, "value": choice.replace(" ", "_")})

    payload = {"type": CallbackTypes.AUTOCOMPLETE_RESULT, "data": {"choices": processed_choices}}
    await self._client.http.post_initial_response(payload, self.interaction_id, self._token)

ModalContext

Bases: InteractionContext

Source code in naff/models/naff/context.py
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
@define
class ModalContext(InteractionContext):
    custom_id: str = field(default="")

    @classmethod
    def from_dict(cls, data: Dict, client: "Client") -> "ModalContext":
        new_cls = super().from_dict(data, client)

        new_cls.kwargs = {
            comp["components"][0]["custom_id"]: comp["components"][0]["value"] for comp in data["data"]["components"]
        }
        new_cls.custom_id = data["data"]["custom_id"]
        return new_cls

    @property
    def responses(self) -> dict[str, str]:
        """
        Get the responses to this modal.

        Returns:
            A dictionary of responses. Keys are the custom_ids of your components.
        """
        return self.kwargs

responses() property

Get the responses to this modal.

Returns:

Type Description
dict[str, str]

A dictionary of responses. Keys are the custom_ids of your components.

Source code in naff/models/naff/context.py
621
622
623
624
625
626
627
628
629
@property
def responses(self) -> dict[str, str]:
    """
    Get the responses to this modal.

    Returns:
        A dictionary of responses. Keys are the custom_ids of your components.
    """
    return self.kwargs

PrefixedContext

Bases: Context, SendMixin

Source code in naff/models/naff/context.py
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
@define
class PrefixedContext(Context, SendMixin):
    prefix: str = field(default=MISSING, metadata=docs("The prefix used to invoke this command"))

    @classmethod
    def from_message(cls, client: "Client", message: "Message") -> "PrefixedContext":
        new_cls = cls(
            client=client,
            message=message,
            author=message.author,
            channel=message.channel,
            guild_id=message._guild_id,
        )
        return new_cls

    @property
    def content_parameters(self) -> str:
        return self.message.content.removeprefix(f"{self.prefix}{self.invoke_target}").strip()

    async def reply(
        self,
        content: Optional[str] = None,
        embeds: Optional[Union[List[Union["Embed", dict]], Union["Embed", dict]]] = None,
        embed: Optional[Union["Embed", dict]] = None,
        **kwargs,
    ) -> "Message":
        """Reply to this message, takes all the same attributes as `send`."""
        return await self.send(content=content, reply_to=self.message, embeds=embeds or embed, **kwargs)

    async def _send_http_request(
        self, message_payload: Union[dict, "FormData"], files: list["UPLOADABLE_TYPE"] | None = None
    ) -> dict:
        return await self._client.http.create_message(message_payload, self.channel.id, files=files)

reply(content=None, embeds=None, embed=None, **kwargs) async

Reply to this message, takes all the same attributes as send.

Source code in naff/models/naff/context.py
651
652
653
654
655
656
657
658
659
async def reply(
    self,
    content: Optional[str] = None,
    embeds: Optional[Union[List[Union["Embed", dict]], Union["Embed", dict]]] = None,
    embed: Optional[Union["Embed", dict]] = None,
    **kwargs,
) -> "Message":
    """Reply to this message, takes all the same attributes as `send`."""
    return await self.send(content=content, reply_to=self.message, embeds=embeds or embed, **kwargs)

HybridContext

Bases: Context

Represents the context for hybrid commands, a slash command that can also be used as a prefixed command.

This attempts to create a compatibility layer to allow contexts for an interaction or a message to be used seamlessly.

Source code in naff/models/naff/context.py
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
@define
class HybridContext(Context):
    """
    Represents the context for hybrid commands, a slash command that can also be used as a prefixed command.

    This attempts to create a compatibility layer to allow contexts for an interaction or a message to be used seamlessly.
    """

    deferred: bool = field(default=False, metadata=docs("Is this context deferred?"))
    responded: bool = field(default=False, metadata=docs("Have we responded to this?"))
    app_permissions: Permissions = field(
        default=0, converter=Permissions, metadata=docs("The permissions this context has")
    )

    _interaction_context: Optional[InteractionContext] = field(default=None)
    _prefixed_context: Optional[PrefixedContext] = field(default=None)

    @classmethod
    def from_interaction_context(cls, context: InteractionContext) -> "HybridContext":
        return cls(
            client=context._client,  # type: ignore
            interaction_context=context,  # type: ignore
            invoke_target=context.invoke_target,
            command=context.command,
            args=context.args,
            kwargs=context.kwargs,
            author=context.author,
            channel=context.channel,
            guild_id=context.guild_id,
            deferred=context.deferred,
            responded=context.responded,
            app_permissions=context.app_permissions,
        )

    @classmethod
    def from_prefixed_context(cls, context: PrefixedContext) -> "HybridContext":
        # this is a "best guess" on what the permissions are
        # this may or may not be totally accurate
        if hasattr(context.channel, "permissions_for"):
            app_permissions = context.channel.permissions_for(context.guild.me)  # type: ignore
        elif context.channel.type in {10, 11, 12}:  # it's a thread
            app_permissions = context.channel.parent_channel.permissions_for(context.guild.me)  # type: ignore
        else:
            # this is what happens with interaction contexts in dms
            app_permissions = 0

        return cls(
            client=context._client,  # type: ignore
            prefixed_context=context,  # type: ignore
            invoke_target=context.invoke_target,
            command=context.command,
            args=context.args,
            kwargs=context.kwargs,  # this is usually empty
            author=context.author,
            channel=context.channel,
            guild_id=context.guild_id,
            message=context.message,
            app_permissions=app_permissions,
        )

    @property
    def inner_context(self) -> InteractionContext | PrefixedContext:
        """
        Returns the context powering the current hybrid context.

        This can be used for scope-specific actions, like sending modals in an interaction.
        """
        return self._interaction_context or self._prefixed_context  # type: ignore

    @property
    def ephemeral(self) -> bool:
        """Returns if responses to this interaction are ephemeral, if this is an interaction. Otherwise, returns False."""
        return self._interaction_context.ephemeral if self._interaction_context else False

    @property
    def expires_at(self) -> Optional[Timestamp]:
        """The timestamp the context is expected to expire at, or None if the context never expires."""
        if not self._interaction_context:
            return None

        if self.responded:
            return Timestamp.from_snowflake(self._interaction_context.interaction_id) + datetime.timedelta(minutes=15)
        return Timestamp.from_snowflake(self._interaction_context.interaction_id) + datetime.timedelta(seconds=3)

    @property
    def expired(self) -> bool:
        """Has the context expired yet?"""
        return Timestamp.utcnow() >= self.expires_at if self.expires_at else False

    @property
    def invoked_name(self) -> str:
        return (
            self.command.get_localised_name(self._interaction_context.locale)
            if self._interaction_context
            else self.invoke_target
        )

    async def defer(self, ephemeral: bool = False) -> None:
        """
        Either defers the response (if used in an interaction) or triggers a typing indicator for 10 seconds (if used for messages).

        Args:
            ephemeral: Should the response be ephemeral? Only applies to responses for interactions.

        """
        if self._interaction_context:
            await self._interaction_context.defer(ephemeral=ephemeral)
        else:
            await self.channel.trigger_typing()

        self.deferred = True

    async def reply(
        self,
        content: Optional[str] = None,
        embeds: Optional[Union[List[Union["Embed", dict]], Union["Embed", dict]]] = None,
        embed: Optional[Union["Embed", dict]] = None,
        **kwargs,
    ) -> "Message":
        """
        Reply to this message, takes all the same attributes as `send`.

        For interactions, this functions the same as `send`.
        """
        kwargs = locals()
        kwargs.pop("self")
        extra_kwargs = kwargs.pop("kwargs")
        kwargs |= extra_kwargs

        if self._interaction_context:
            result = await self._interaction_context.send(**kwargs)
        else:
            kwargs.pop("ephemeral", None)
            result = await self._prefixed_context.reply(**kwargs)  # type: ignore

        self.responded = True
        return result

    async def send(
        self,
        content: Optional[str] = None,
        embeds: Optional[Union[List[Union["Embed", dict]], Union["Embed", dict]]] = None,
        embed: Optional[Union["Embed", dict]] = None,
        components: Optional[
            Union[
                List[List[Union["BaseComponent", dict]]],
                List[Union["BaseComponent", dict]],
                "BaseComponent",
                dict,
            ]
        ] = None,
        stickers: Optional[Union[List[Union["Sticker", "Snowflake_Type"]], "Sticker", "Snowflake_Type"]] = None,
        allowed_mentions: Optional[Union["AllowedMentions", dict]] = None,
        reply_to: Optional[Union["MessageReference", "Message", dict, "Snowflake_Type"]] = None,
        file: Optional[Union["File", "IOBase", "Path", str]] = None,
        tts: bool = False,
        flags: Optional[Union[int, "MessageFlags"]] = None,
        ephemeral: bool = False,
        **kwargs,
    ) -> "Message":
        """
        Send a message.

        Args:
            content: Message text content.
            embeds: Embedded rich content (up to 6000 characters).
            embed: Embedded rich content (up to 6000 characters).
            components: The components to include with the message.
            stickers: IDs of up to 3 stickers in the server to send in the message.
            allowed_mentions: Allowed mentions for the message.
            reply_to: Message to reference, must be from the same channel.
            files: Files to send, the path, bytes or File() instance, defaults to None. You may have up to 10 files.
            file: Files to send, the path, bytes or File() instance, defaults to None. You may have up to 10 files.
            tts: Should this message use Text To Speech.
            suppress_embeds: Should embeds be suppressed on this send
            flags: Message flags to apply.
            ephemeral: Should this message be sent as ephemeral (hidden) - only works with interactions.

        Returns:
            New message object that was sent.

        """
        kwargs = locals()
        kwargs.pop("self")
        extra_kwargs = kwargs.pop("kwargs")
        kwargs |= extra_kwargs

        if self._interaction_context:
            result = await self._interaction_context.send(**kwargs)
        else:
            kwargs.pop("ephemeral", None)
            result = await self._prefixed_context.send(**kwargs)  # type: ignore

        self.responded = True
        return result

inner_context() property

Returns the context powering the current hybrid context.

This can be used for scope-specific actions, like sending modals in an interaction.

Source code in naff/models/naff/context.py
727
728
729
730
731
732
733
734
@property
def inner_context(self) -> InteractionContext | PrefixedContext:
    """
    Returns the context powering the current hybrid context.

    This can be used for scope-specific actions, like sending modals in an interaction.
    """
    return self._interaction_context or self._prefixed_context  # type: ignore

ephemeral() property

Returns if responses to this interaction are ephemeral, if this is an interaction. Otherwise, returns False.

Source code in naff/models/naff/context.py
736
737
738
739
@property
def ephemeral(self) -> bool:
    """Returns if responses to this interaction are ephemeral, if this is an interaction. Otherwise, returns False."""
    return self._interaction_context.ephemeral if self._interaction_context else False

expires_at() property

The timestamp the context is expected to expire at, or None if the context never expires.

Source code in naff/models/naff/context.py
741
742
743
744
745
746
747
748
749
@property
def expires_at(self) -> Optional[Timestamp]:
    """The timestamp the context is expected to expire at, or None if the context never expires."""
    if not self._interaction_context:
        return None

    if self.responded:
        return Timestamp.from_snowflake(self._interaction_context.interaction_id) + datetime.timedelta(minutes=15)
    return Timestamp.from_snowflake(self._interaction_context.interaction_id) + datetime.timedelta(seconds=3)

expired() property

Has the context expired yet?

Source code in naff/models/naff/context.py
751
752
753
754
@property
def expired(self) -> bool:
    """Has the context expired yet?"""
    return Timestamp.utcnow() >= self.expires_at if self.expires_at else False

defer(ephemeral=False) async

Either defers the response (if used in an interaction) or triggers a typing indicator for 10 seconds (if used for messages).

Parameters:

Name Type Description Default
ephemeral bool

Should the response be ephemeral? Only applies to responses for interactions.

False
Source code in naff/models/naff/context.py
764
765
766
767
768
769
770
771
772
773
774
775
776
777
async def defer(self, ephemeral: bool = False) -> None:
    """
    Either defers the response (if used in an interaction) or triggers a typing indicator for 10 seconds (if used for messages).

    Args:
        ephemeral: Should the response be ephemeral? Only applies to responses for interactions.

    """
    if self._interaction_context:
        await self._interaction_context.defer(ephemeral=ephemeral)
    else:
        await self.channel.trigger_typing()

    self.deferred = True

reply(content=None, embeds=None, embed=None, **kwargs) async

Reply to this message, takes all the same attributes as send.

For interactions, this functions the same as send.

Source code in naff/models/naff/context.py
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
async def reply(
    self,
    content: Optional[str] = None,
    embeds: Optional[Union[List[Union["Embed", dict]], Union["Embed", dict]]] = None,
    embed: Optional[Union["Embed", dict]] = None,
    **kwargs,
) -> "Message":
    """
    Reply to this message, takes all the same attributes as `send`.

    For interactions, this functions the same as `send`.
    """
    kwargs = locals()
    kwargs.pop("self")
    extra_kwargs = kwargs.pop("kwargs")
    kwargs |= extra_kwargs

    if self._interaction_context:
        result = await self._interaction_context.send(**kwargs)
    else:
        kwargs.pop("ephemeral", None)
        result = await self._prefixed_context.reply(**kwargs)  # type: ignore

    self.responded = True
    return result

send(content=None, embeds=None, embed=None, components=None, stickers=None, allowed_mentions=None, reply_to=None, file=None, tts=False, flags=None, ephemeral=False, **kwargs) async

Send a message.

Parameters:

Name Type Description Default
content Optional[str]

Message text content.

None
embeds Optional[Union[List[Union[Embed, dict]], Union[Embed, dict]]]

Embedded rich content (up to 6000 characters).

None
embed Optional[Union[Embed, dict]]

Embedded rich content (up to 6000 characters).

None
components Optional[Union[List[List[Union[BaseComponent, dict]]], List[Union[BaseComponent, dict]], BaseComponent, dict]]

The components to include with the message.

None
stickers Optional[Union[List[Union[Sticker, Snowflake_Type]], Sticker, Snowflake_Type]]

IDs of up to 3 stickers in the server to send in the message.

None
allowed_mentions Optional[Union[AllowedMentions, dict]]

Allowed mentions for the message.

None
reply_to Optional[Union[MessageReference, Message, dict, Snowflake_Type]]

Message to reference, must be from the same channel.

None
files

Files to send, the path, bytes or File() instance, defaults to None. You may have up to 10 files.

required
file Optional[Union[File, IOBase, Path, str]]

Files to send, the path, bytes or File() instance, defaults to None. You may have up to 10 files.

None
tts bool

Should this message use Text To Speech.

False
suppress_embeds

Should embeds be suppressed on this send

required
flags Optional[Union[int, MessageFlags]]

Message flags to apply.

None
ephemeral bool

Should this message be sent as ephemeral (hidden) - only works with interactions.

False

Returns:

Type Description
Message

New message object that was sent.

Source code in naff/models/naff/context.py
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
async def send(
    self,
    content: Optional[str] = None,
    embeds: Optional[Union[List[Union["Embed", dict]], Union["Embed", dict]]] = None,
    embed: Optional[Union["Embed", dict]] = None,
    components: Optional[
        Union[
            List[List[Union["BaseComponent", dict]]],
            List[Union["BaseComponent", dict]],
            "BaseComponent",
            dict,
        ]
    ] = None,
    stickers: Optional[Union[List[Union["Sticker", "Snowflake_Type"]], "Sticker", "Snowflake_Type"]] = None,
    allowed_mentions: Optional[Union["AllowedMentions", dict]] = None,
    reply_to: Optional[Union["MessageReference", "Message", dict, "Snowflake_Type"]] = None,
    file: Optional[Union["File", "IOBase", "Path", str]] = None,
    tts: bool = False,
    flags: Optional[Union[int, "MessageFlags"]] = None,
    ephemeral: bool = False,
    **kwargs,
) -> "Message":
    """
    Send a message.

    Args:
        content: Message text content.
        embeds: Embedded rich content (up to 6000 characters).
        embed: Embedded rich content (up to 6000 characters).
        components: The components to include with the message.
        stickers: IDs of up to 3 stickers in the server to send in the message.
        allowed_mentions: Allowed mentions for the message.
        reply_to: Message to reference, must be from the same channel.
        files: Files to send, the path, bytes or File() instance, defaults to None. You may have up to 10 files.
        file: Files to send, the path, bytes or File() instance, defaults to None. You may have up to 10 files.
        tts: Should this message use Text To Speech.
        suppress_embeds: Should embeds be suppressed on this send
        flags: Message flags to apply.
        ephemeral: Should this message be sent as ephemeral (hidden) - only works with interactions.

    Returns:
        New message object that was sent.

    """
    kwargs = locals()
    kwargs.pop("self")
    extra_kwargs = kwargs.pop("kwargs")
    kwargs |= extra_kwargs

    if self._interaction_context:
        result = await self._interaction_context.send(**kwargs)
    else:
        kwargs.pop("ephemeral", None)
        result = await self._prefixed_context.send(**kwargs)  # type: ignore

    self.responded = True
    return result

SendableContext

Bases: Protocol

A protocol that supports any context that can send messages.

Use it to type hint something that accepts both PrefixedContext and InteractionContext.

Source code in naff/models/naff/context.py
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
@runtime_checkable
class SendableContext(Protocol):
    """
    A protocol that supports any context that can send messages.

    Use it to type hint something that accepts both PrefixedContext and InteractionContext.
    """

    channel: "TYPE_MESSAGEABLE_CHANNEL"
    invoke_target: str

    author: Union["Member", "User"]
    guild_id: "Snowflake_Type"
    message: "Message"

    @property
    def bot(self) -> "Client":
        ...

    @property
    def guild(self) -> Optional["Guild"]:
        ...

    async def send(
        self,
        content: Optional[str] = None,
        embeds: Optional[Union[List[Union["Embed", dict]], Union["Embed", dict]]] = None,
        components: Optional[
            Union[
                List[List[Union["BaseComponent", dict]]],
                List[Union["BaseComponent", dict]],
                "BaseComponent",
                dict,
            ]
        ] = None,
        stickers: Optional[Union[List[Union["Sticker", "Snowflake_Type"]], "Sticker", "Snowflake_Type"]] = None,
        allowed_mentions: Optional[Union["AllowedMentions", dict]] = None,
        reply_to: Optional[Union["MessageReference", "Message", dict, "Snowflake_Type"]] = None,
        file: Optional[Union["File", "IOBase", "Path", str]] = None,
        tts: bool = False,
        flags: Optional[Union[int, "MessageFlags"]] = None,
        **kwargs: Any,
    ) -> "Message":
        ...

Presence

ActivityTimestamps

Bases: DictSerializationMixin

Source code in naff/models/discord/activity.py
21
22
23
24
25
26
@define()
class ActivityTimestamps(DictSerializationMixin):
    start: Optional[Timestamp] = field(default=None, converter=optional(timestamp_converter))
    """The start time of the activity. Shows "elapsed" timer on discord client."""
    end: Optional[Timestamp] = field(default=None, converter=optional(timestamp_converter))
    """The end time of the activity. Shows "remaining" timer on discord client."""

start: Optional[Timestamp] = field(default=None, converter=optional(timestamp_converter)) class-attribute

The start time of the activity. Shows "elapsed" timer on discord client.

end: Optional[Timestamp] = field(default=None, converter=optional(timestamp_converter)) class-attribute

The end time of the activity. Shows "remaining" timer on discord client.

ActivityParty

Bases: DictSerializationMixin

Source code in naff/models/discord/activity.py
29
30
31
32
33
34
@define()
class ActivityParty(DictSerializationMixin):
    id: Optional[str] = field(default=None)
    """A unique identifier for this party"""
    size: Optional[List[int]] = field(default=None)
    """Info about the size of the party"""

id: Optional[str] = field(default=None) class-attribute

A unique identifier for this party

size: Optional[List[int]] = field(default=None) class-attribute

Info about the size of the party

ActivityAssets

Bases: DictSerializationMixin

Source code in naff/models/discord/activity.py
37
38
39
40
41
42
43
44
45
46
@define()
class ActivityAssets(DictSerializationMixin):
    large_image: Optional[str] = field(default=None)
    """The large image for this activity. Uses discord's asset image url format."""
    large_text: Optional[str] = field(default=None)
    """Hover text for the large image"""
    small_image: Optional[str] = field(default=None)
    """The large image for this activity. Uses discord's asset image url format."""
    small_text: Optional[str] = field(default=None)
    """Hover text for the small image"""

large_image: Optional[str] = field(default=None) class-attribute

The large image for this activity. Uses discord's asset image url format.

large_text: Optional[str] = field(default=None) class-attribute

Hover text for the large image

small_image: Optional[str] = field(default=None) class-attribute

The large image for this activity. Uses discord's asset image url format.

small_text: Optional[str] = field(default=None) class-attribute

Hover text for the small image

ActivitySecrets

Bases: DictSerializationMixin

Source code in naff/models/discord/activity.py
49
50
51
52
53
54
55
56
@define()
class ActivitySecrets(DictSerializationMixin):
    join: Optional[str] = field(default=None)
    """The secret for joining a party"""
    spectate: Optional[str] = field(default=None)
    """The secret for spectating a party"""
    match: Optional[str] = field(default=None)
    """The secret for a specific instanced match"""

join: Optional[str] = field(default=None) class-attribute

The secret for joining a party

spectate: Optional[str] = field(default=None) class-attribute

The secret for spectating a party

match: Optional[str] = field(default=None) class-attribute

The secret for a specific instanced match

Activity

Bases: DictSerializationMixin

Represents a discord activity object use for rich presence in discord.

Source code in naff/models/discord/activity.py
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
@define(kw_only=False)
class Activity(DictSerializationMixin):
    """Represents a discord activity object use for rich presence in discord."""

    name: str = field(repr=True)
    """The activity's name"""
    type: ActivityType = field(repr=True, default=ActivityType.GAME)
    """The type of activity"""
    url: Optional[str] = field(repr=True, default=None)
    """Stream url, is validated when type is 1"""
    created_at: Optional[Timestamp] = field(repr=True, default=None, converter=optional(timestamp_converter))
    """When the activity was added to the user's session"""
    timestamps: Optional[ActivityTimestamps] = field(default=None, converter=optional(ActivityTimestamps.from_dict))
    """Start and/or end of the game"""
    application_id: "Snowflake_Type" = field(default=None)
    """Application id for the game"""
    details: Optional[str] = field(default=None)
    """What the player is currently doing"""
    state: Optional[str] = field(default=None)
    """The user's current party status"""
    emoji: Optional[PartialEmoji] = field(default=None, converter=optional(PartialEmoji.from_dict))
    """The emoji used for a custom status"""
    party: Optional[ActivityParty] = field(default=None, converter=optional(ActivityParty.from_dict))
    """Information for the current party of the player"""
    assets: Optional[ActivityAssets] = field(default=None, converter=optional(ActivityAssets.from_dict))
    """Assets to display on the player's profile"""
    secrets: Optional[ActivitySecrets] = field(default=None, converter=optional(ActivitySecrets.from_dict))
    """Secrets for Rich Presence joining and spectating"""
    instance: Optional[bool] = field(default=False)
    """Whether or not the activity is an instanced game session"""
    flags: Optional[ActivityFlags] = field(default=None, converter=optional(ActivityFlags))
    """Activity flags bitwise OR together, describes what the payload includes"""
    buttons: List[str] = field(factory=list)
    """The custom buttons shown in the Rich Presence (max 2)"""

    @classmethod
    def create(cls, name: str, type: ActivityType = ActivityType.GAME, url: Optional[str] = None) -> "Activity":
        """
        Creates an activity object for the bot.

        Args:
            name: The new activity's name
            type: Type of activity to create
            url: Stream link for the activity

        Returns:
            The new activity object

        """
        return cls(name=name, type=type, url=url)  # noqa

    def to_dict(self) -> dict:
        return dict_filter_none({"name": self.name, "type": self.type, "url": self.url})

name: str = field(repr=True) class-attribute

The activity's name

type: ActivityType = field(repr=True, default=ActivityType.GAME) class-attribute

The type of activity

url: Optional[str] = field(repr=True, default=None) class-attribute

Stream url, is validated when type is 1

created_at: Optional[Timestamp] = field(repr=True, default=None, converter=optional(timestamp_converter)) class-attribute

When the activity was added to the user's session

timestamps: Optional[ActivityTimestamps] = field(default=None, converter=optional(ActivityTimestamps.from_dict)) class-attribute

Start and/or end of the game

application_id: Snowflake_Type = field(default=None) class-attribute

Application id for the game

details: Optional[str] = field(default=None) class-attribute

What the player is currently doing

state: Optional[str] = field(default=None) class-attribute

The user's current party status

emoji: Optional[PartialEmoji] = field(default=None, converter=optional(PartialEmoji.from_dict)) class-attribute

The emoji used for a custom status

party: Optional[ActivityParty] = field(default=None, converter=optional(ActivityParty.from_dict)) class-attribute

Information for the current party of the player

assets: Optional[ActivityAssets] = field(default=None, converter=optional(ActivityAssets.from_dict)) class-attribute

Assets to display on the player's profile

secrets: Optional[ActivitySecrets] = field(default=None, converter=optional(ActivitySecrets.from_dict)) class-attribute

Secrets for Rich Presence joining and spectating

instance: Optional[bool] = field(default=False) class-attribute

Whether or not the activity is an instanced game session

flags: Optional[ActivityFlags] = field(default=None, converter=optional(ActivityFlags)) class-attribute

Activity flags bitwise OR together, describes what the payload includes

buttons: List[str] = field(factory=list) class-attribute

The custom buttons shown in the Rich Presence (max 2)

create(name, type=ActivityType.GAME, url=None) classmethod

Creates an activity object for the bot.

Parameters:

Name Type Description Default
name str

The new activity's name

required
type ActivityType

Type of activity to create

ActivityType.GAME
url Optional[str]

Stream link for the activity

None

Returns:

Type Description
Activity

The new activity object

Source code in naff/models/discord/activity.py
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
@classmethod
def create(cls, name: str, type: ActivityType = ActivityType.GAME, url: Optional[str] = None) -> "Activity":
    """
    Creates an activity object for the bot.

    Args:
        name: The new activity's name
        type: Type of activity to create
        url: Stream link for the activity

    Returns:
        The new activity object

    """
    return cls(name=name, type=type, url=url)  # noqa

Data

Application

Bases: DiscordObject

Represents a discord application.

Source code in naff/models/discord/application.py
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
@define()
class Application(DiscordObject):
    """Represents a discord application."""

    name: str = field(repr=True)
    """The name of the application"""
    icon: Optional[Asset] = field(default=None)
    """The icon of the application"""
    description: Optional[str] = field(default=None)
    """The description of the application"""
    rpc_origins: Optional[List[str]] = field(default=None)
    """An array of rpc origin urls, if rpc is enabled"""
    bot_public: bool = field(default=True)
    """When false only app owner can join the app's bot to guilds"""
    bot_require_code_grant: bool = field(default=False)
    """When true the app's bot will only join upon completion of the full oauth2 code grant flow"""
    terms_of_service_url: Optional[str] = field(default=None)
    """The url of the app's terms of service"""
    privacy_policy_url: Optional[str] = field(default=None)
    """The url of the app's privacy policy"""
    owner_id: Optional[Snowflake_Type] = field(default=None, converter=optional(to_snowflake))
    """The id of the owner of the application"""
    summary: str = field()
    """If this application is a game sold on Discord, this field will be the summary field for the store page of its primary sku"""
    verify_key: Optional[str] = field(default=MISSING)
    """The hex encoded key for verification in interactions and the GameSDK's GetTicket"""
    team: Optional["Team"] = field(default=None)
    """If the application belongs to a team, this will be a list of the members of that team"""
    guild_id: Optional["Snowflake_Type"] = field(default=None)
    """If this application is a game sold on Discord, this field will be the guild to which it has been linked"""
    primary_sku_id: Optional["Snowflake_Type"] = field(default=None)
    """If this application is a game sold on Discord, this field will be the id of the "Game SKU" that is created, if exists"""
    slug: Optional[str] = field(default=None)
    """If this application is a game sold on Discord, this field will be the URL slug that links to the store page"""
    cover_image: Optional[Asset] = field(default=None)
    """The application's default rich presence invite cover"""
    flags: Optional["ApplicationFlags"] = field(default=None, converter=optional(ApplicationFlags))
    """The application's public flags"""
    tags: Optional[List[str]] = field(default=None)
    """The application's tags describing its functionality and content"""
    # todo: implement an ApplicationInstallParams object. See https://discord.com/developers/docs/resources/application#install-params-object
    install_params: Optional[dict] = field(default=None)
    """The application's settings for in-app invitation to guilds"""
    custom_install_url: Optional[str] = field(default=None)
    """The application's custom authorization link for invitation to a guild"""

    @classmethod
    def _process_dict(cls, data: Dict[str, Any], client: "Client") -> Dict[str, Any]:
        if data.get("team"):
            data["team"] = Team.from_dict(data["team"], client)
            data["owner_id"] = data["team"].owner_user_id
        else:
            if "owner" in data:
                owner = client.cache.place_user_data(data.pop("owner"))
                data["owner_id"] = owner.id

        if data.get("icon"):
            data["icon"] = Asset.from_path_hash(client, f"app-icons/{data['id']}/{{}}", data["icon"])
        if data.get("cover_image"):
            data["cover_image"] = Asset.from_path_hash(client, f"app-icons/{data['id']}/{{}}", data["cover_image"])

        return data

    @property
    def owner(self) -> "User":
        """The user object for the owner of this application"""
        return self._client.cache.get_user(self.owner_id)

name: str = field(repr=True) class-attribute

The name of the application

icon: Optional[Asset] = field(default=None) class-attribute

The icon of the application

description: Optional[str] = field(default=None) class-attribute

The description of the application

rpc_origins: Optional[List[str]] = field(default=None) class-attribute

An array of rpc origin urls, if rpc is enabled

bot_public: bool = field(default=True) class-attribute

When false only app owner can join the app's bot to guilds

bot_require_code_grant: bool = field(default=False) class-attribute

When true the app's bot will only join upon completion of the full oauth2 code grant flow

terms_of_service_url: Optional[str] = field(default=None) class-attribute

The url of the app's terms of service

privacy_policy_url: Optional[str] = field(default=None) class-attribute

The url of the app's privacy policy

owner_id: Optional[Snowflake_Type] = field(default=None, converter=optional(to_snowflake)) class-attribute

The id of the owner of the application

summary: str = field() class-attribute

If this application is a game sold on Discord, this field will be the summary field for the store page of its primary sku

verify_key: Optional[str] = field(default=MISSING) class-attribute

The hex encoded key for verification in interactions and the GameSDK's GetTicket

team: Optional[Team] = field(default=None) class-attribute

If the application belongs to a team, this will be a list of the members of that team

guild_id: Optional[Snowflake_Type] = field(default=None) class-attribute

If this application is a game sold on Discord, this field will be the guild to which it has been linked

primary_sku_id: Optional[Snowflake_Type] = field(default=None) class-attribute

If this application is a game sold on Discord, this field will be the id of the "Game SKU" that is created, if exists

slug: Optional[str] = field(default=None) class-attribute

If this application is a game sold on Discord, this field will be the URL slug that links to the store page

cover_image: Optional[Asset] = field(default=None) class-attribute

The application's default rich presence invite cover

flags: Optional[ApplicationFlags] = field(default=None, converter=optional(ApplicationFlags)) class-attribute

The application's public flags

tags: Optional[List[str]] = field(default=None) class-attribute

The application's tags describing its functionality and content

install_params: Optional[dict] = field(default=None) class-attribute

The application's settings for in-app invitation to guilds

custom_install_url: Optional[str] = field(default=None) class-attribute

The application's custom authorization link for invitation to a guild

owner() property

The user object for the owner of this application

Source code in naff/models/discord/application.py
82
83
84
85
@property
def owner(self) -> "User":
    """The user object for the owner of this application"""
    return self._client.cache.get_user(self.owner_id)

TeamMember

Bases: DiscordObject

Source code in naff/models/discord/team.py
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
@define()
class TeamMember(DiscordObject):
    membership_state: TeamMembershipState = field(converter=TeamMembershipState)
    """Rhe user's membership state on the team"""
    # permissions: List[str] = field(default=["*"])  # disabled until discord adds more team roles
    team_id: "Snowflake_Type" = field(repr=True)
    """Rhe id of the parent team of which they are a member"""
    user: "User" = field()  # TODO: cache partial user (avatar, discrim, id, username)
    """Rhe avatar, discriminator, id, and username of the user"""

    @classmethod
    def _process_dict(cls, data: Dict[str, Any], client: "Client") -> Dict[str, Any]:
        data["user"] = client.cache.place_user_data(data["user"])
        data["id"] = data["user"].id
        return data

membership_state: TeamMembershipState = field(converter=TeamMembershipState) class-attribute

Rhe user's membership state on the team

team_id: Snowflake_Type = field(repr=True) class-attribute

Rhe id of the parent team of which they are a member

user: User = field() class-attribute

Rhe avatar, discriminator, id, and username of the user

Team

Bases: DiscordObject

Source code in naff/models/discord/team.py
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
@define()
class Team(DiscordObject):
    icon: Optional[Asset] = field(default=None)
    """A hash of the image of the team's icon"""
    members: List[TeamMember] = field(factory=list)
    """The members of the team"""
    name: str = field(repr=True)
    """The name of the team"""
    owner_user_id: "Snowflake_Type" = field(converter=to_snowflake)
    """The user id of the current team owner"""

    @classmethod
    def _process_dict(cls, data: Dict[str, Any], client: "Client") -> Dict[str, Any]:
        data["members"] = TeamMember.from_list(data["members"], client)
        if data["icon"]:
            data["icon"] = Asset.from_path_hash(client, f"team-icons/{data['id']}/{{}}", data["icon"])
        return data

    @property
    def owner(self) -> "User":
        """The owner of the team"""
        return self._client.cache.get_user(self.owner_user_id)

    def is_in_team(self, user: Union["SnowflakeObject", "Snowflake_Type"]) -> bool:
        """
        Returns True if the passed user or ID is a member within the team.

        Args:
            user: The user or user ID to check

        Returns:
            Boolean indicating whether the user is in the team
        """
        return to_snowflake(user) in [m.id for m in self.members]

icon: Optional[Asset] = field(default=None) class-attribute

A hash of the image of the team's icon

members: List[TeamMember] = field(factory=list) class-attribute

The members of the team

name: str = field(repr=True) class-attribute

The name of the team

owner_user_id: Snowflake_Type = field(converter=to_snowflake) class-attribute

The user id of the current team owner

owner() property

The owner of the team

Source code in naff/models/discord/team.py
52
53
54
55
@property
def owner(self) -> "User":
    """The owner of the team"""
    return self._client.cache.get_user(self.owner_user_id)

is_in_team(user)

Returns True if the passed user or ID is a member within the team.

Parameters:

Name Type Description Default
user Union[SnowflakeObject, Snowflake_Type]

The user or user ID to check

required

Returns:

Type Description
bool

Boolean indicating whether the user is in the team

Source code in naff/models/discord/team.py
57
58
59
60
61
62
63
64
65
66
67
def is_in_team(self, user: Union["SnowflakeObject", "Snowflake_Type"]) -> bool:
    """
    Returns True if the passed user or ID is a member within the team.

    Args:
        user: The user or user ID to check

    Returns:
        Boolean indicating whether the user is in the team
    """
    return to_snowflake(user) in [m.id for m in self.members]

CursedIntEnum

Bases: IntEnum

Source code in naff/models/discord/enums.py
102
103
104
105
106
class CursedIntEnum(IntEnum):
    @classmethod
    def _missing_(cls: Type[SELF], value) -> SELF:
        """Construct a new enum item to represent this new unknown type - without losing the value"""
        return _return_cursed_enum(cls, value)

WebSocketOPCodes

Bases: CursedIntEnum

Codes used by the Gateway to signify events.

Source code in naff/models/discord/enums.py
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
class WebSocketOPCodes(CursedIntEnum):
    """Codes used by the Gateway to signify events."""

    DISPATCH = 0
    """An event was dispatched"""
    HEARTBEAT = 1
    """Fired periodically by the client to keep the connection alive"""
    IDENTIFY = 2
    """Starts a new session during the initial handshake."""
    PRESENCE = 3
    """Update the client's presence."""
    VOICE_STATE = 4
    """Used to join/leave or move between voice channels."""
    VOICE_PING = 5
    RESUME = 6
    """Resume a previous session that was disconnected."""
    RECONNECT = 7
    """You should attempt to reconnect and resume immediately."""
    REQUEST_MEMBERS = 8
    """Request information about offline guild members in a large guild."""
    INVALIDATE_SESSION = 9
    """The session has been invalidated. You should reconnect and identify/resume accordingly."""
    HELLO = 10
    """Sent immediately after connecting, contains the `heartbeat_interval` to use."""
    HEARTBEAT_ACK = 11
    """Sent in response to receiving a heartbeat to acknowledge that it has been received."""
    GUILD_SYNC = 12

DISPATCH = 0 class-attribute

An event was dispatched

HEARTBEAT = 1 class-attribute

Fired periodically by the client to keep the connection alive

IDENTIFY = 2 class-attribute

Starts a new session during the initial handshake.

PRESENCE = 3 class-attribute

Update the client's presence.

VOICE_STATE = 4 class-attribute

Used to join/leave or move between voice channels.

RESUME = 6 class-attribute

Resume a previous session that was disconnected.

RECONNECT = 7 class-attribute

You should attempt to reconnect and resume immediately.

REQUEST_MEMBERS = 8 class-attribute

Request information about offline guild members in a large guild.

INVALIDATE_SESSION = 9 class-attribute

The session has been invalidated. You should reconnect and identify/resume accordingly.

HELLO = 10 class-attribute

Sent immediately after connecting, contains the heartbeat_interval to use.

HEARTBEAT_ACK = 11 class-attribute

Sent in response to receiving a heartbeat to acknowledge that it has been received.

Intents

Bases: DiscordIntFlag

When identifying to the gateway, you can specify an intents parameter which allows you to conditionally subscribe to pre-defined "intents", groups of events defined by Discord.

info

For details about what intents do, or which intents you'll want, please read the Discord API Documentation

Source code in naff/models/discord/enums.py
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
class Intents(DiscordIntFlag):  # type: ignore
    """
    When identifying to the gateway, you can specify an intents parameter which allows you to conditionally subscribe to pre-defined "intents", groups of events defined by Discord.

    info:
        For details about what intents do, or which intents you'll want, please read the [Discord API Documentation](https://discord.com/developers/docs/topics/gateway#gateway-intents)

    """

    GUILDS = 1 << 0
    GUILD_MEMBERS = 1 << 1
    GUILD_BANS = 1 << 2
    GUILD_EMOJIS_AND_STICKERS = 1 << 3
    GUILD_INTEGRATIONS = 1 << 4
    GUILD_WEBHOOKS = 1 << 5
    GUILD_INVITES = 1 << 6
    GUILD_VOICE_STATES = 1 << 7
    GUILD_PRESENCES = 1 << 8
    GUILD_MESSAGES = 1 << 9
    GUILD_MESSAGE_REACTIONS = 1 << 10
    GUILD_MESSAGE_TYPING = 1 << 11
    DIRECT_MESSAGES = 1 << 12
    DIRECT_MESSAGE_REACTIONS = 1 << 13
    DIRECT_MESSAGE_TYPING = 1 << 14
    GUILD_MESSAGE_CONTENT = 1 << 15
    GUILD_SCHEDULED_EVENTS = 1 << 16
    AUTO_MODERATION_CONFIGURATION = 1 << 20
    AUTO_MODERATION_EXECUTION = 1 << 21

    # Shortcuts/grouping/aliases
    MESSAGES = GUILD_MESSAGES | DIRECT_MESSAGES
    REACTIONS = GUILD_MESSAGE_REACTIONS | DIRECT_MESSAGE_REACTIONS
    TYPING = GUILD_MESSAGE_TYPING | DIRECT_MESSAGE_TYPING
    AUTO_MOD = AUTO_MODERATION_CONFIGURATION | AUTO_MODERATION_EXECUTION

    PRIVILEGED = GUILD_PRESENCES | GUILD_MEMBERS | GUILD_MESSAGE_CONTENT
    NON_PRIVILEGED = AntiFlag(PRIVILEGED)
    DEFAULT = NON_PRIVILEGED

    # Special members
    NONE = 0
    ALL = AntiFlag()

    @classmethod
    def new(
        cls,
        guilds=False,
        guild_members=False,
        guild_bans=False,
        guild_emojis_and_stickers=False,
        guild_integrations=False,
        guild_webhooks=False,
        guild_invites=False,
        guild_voice_states=False,
        guild_presences=False,
        guild_messages=False,
        guild_message_reactions=False,
        guild_message_typing=False,
        direct_messages=False,
        direct_message_reactions=False,
        direct_message_typing=False,
        guild_message_content=False,
        guild_scheduled_events=False,
        messages=False,
        reactions=False,
        typing=False,
        privileged=False,
        non_privileged=False,
        default=True,
        all=False,
    ) -> "Intents":
        """Set your desired intents."""
        kwargs = locals()
        del kwargs["cls"]

        intents = cls.NONE
        for key in kwargs:
            if kwargs[key]:
                intents |= getattr(cls, key.upper())
        return intents

new(guilds=False, guild_members=False, guild_bans=False, guild_emojis_and_stickers=False, guild_integrations=False, guild_webhooks=False, guild_invites=False, guild_voice_states=False, guild_presences=False, guild_messages=False, guild_message_reactions=False, guild_message_typing=False, direct_messages=False, direct_message_reactions=False, direct_message_typing=False, guild_message_content=False, guild_scheduled_events=False, messages=False, reactions=False, typing=False, privileged=False, non_privileged=False, default=True, all=False) classmethod

Set your desired intents.

Source code in naff/models/discord/enums.py
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
@classmethod
def new(
    cls,
    guilds=False,
    guild_members=False,
    guild_bans=False,
    guild_emojis_and_stickers=False,
    guild_integrations=False,
    guild_webhooks=False,
    guild_invites=False,
    guild_voice_states=False,
    guild_presences=False,
    guild_messages=False,
    guild_message_reactions=False,
    guild_message_typing=False,
    direct_messages=False,
    direct_message_reactions=False,
    direct_message_typing=False,
    guild_message_content=False,
    guild_scheduled_events=False,
    messages=False,
    reactions=False,
    typing=False,
    privileged=False,
    non_privileged=False,
    default=True,
    all=False,
) -> "Intents":
    """Set your desired intents."""
    kwargs = locals()
    del kwargs["cls"]

    intents = cls.NONE
    for key in kwargs:
        if kwargs[key]:
            intents |= getattr(cls, key.upper())
    return intents

UserFlags

Bases: DiscordIntFlag

Flags a user can have.

Source code in naff/models/discord/enums.py
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
class UserFlags(DiscordIntFlag):  # type: ignore
    """Flags a user can have."""

    DISCORD_EMPLOYEE = 1 << 0
    """This person works for Discord"""
    PARTNERED_SERVER_OWNER = 1 << 1
    """User owns a partnered server"""
    HYPESQUAD_EVENTS = 1 << 2
    """User has helped with a hypesquad event"""
    BUG_HUNTER_LEVEL_1 = 1 << 3
    """User has passed the bug hunters quiz"""

    HOUSE_BRAVERY = 1 << 6
    """User belongs to the `bravery` house"""
    HOUSE_BRILLIANCE = 1 << 7
    """User belongs to the `brilliance` house"""
    HOUSE_BALANCE = 1 << 8
    """User belongs to the `balance` house"""
    EARLY_SUPPORTER = 1 << 9
    """This person had Nitro prior to Wednesday, October 10th, 2018"""

    TEAM_USER = 1 << 10
    """A team user"""

    BUG_HUNTER_LEVEL_2 = 1 << 14
    """User is a bug hunter level 2"""

    VERIFIED_BOT = 1 << 16
    """This bot has been verified by Discord"""
    EARLY_VERIFIED_BOT_DEVELOPER = 1 << 17
    """This user was one of the first to be verified"""
    DISCORD_CERTIFIED_MODERATOR = 1 << 18
    """This user is a certified moderator"""

    BOT_HTTP_INTERACTIONS = 1 << 19
    """Bot uses only HTTP interactions and is shown in the online member list"""

    SPAMMER = 1 << 20
    """A user who is suspected of spamming"""
    DISABLE_PREMIUM = 1 << 21
    """Nitro features disabled for this user. Only used by Discord Staff for testing"""

    # Shortcuts/grouping/aliases
    HYPESQUAD = HOUSE_BRAVERY | HOUSE_BRILLIANCE | HOUSE_BALANCE
    BUG_HUNTER = BUG_HUNTER_LEVEL_1 | BUG_HUNTER_LEVEL_2

    # Special members
    NONE = 0
    ALL = AntiFlag()

DISCORD_EMPLOYEE = 1 << 0 class-attribute

This person works for Discord

PARTNERED_SERVER_OWNER = 1 << 1 class-attribute

User owns a partnered server

HYPESQUAD_EVENTS = 1 << 2 class-attribute

User has helped with a hypesquad event

BUG_HUNTER_LEVEL_1 = 1 << 3 class-attribute

User has passed the bug hunters quiz

HOUSE_BRAVERY = 1 << 6 class-attribute

User belongs to the bravery house

HOUSE_BRILLIANCE = 1 << 7 class-attribute

User belongs to the brilliance house

HOUSE_BALANCE = 1 << 8 class-attribute

User belongs to the balance house

EARLY_SUPPORTER = 1 << 9 class-attribute

This person had Nitro prior to Wednesday, October 10th, 2018

TEAM_USER = 1 << 10 class-attribute

A team user

BUG_HUNTER_LEVEL_2 = 1 << 14 class-attribute

User is a bug hunter level 2

VERIFIED_BOT = 1 << 16 class-attribute

This bot has been verified by Discord

EARLY_VERIFIED_BOT_DEVELOPER = 1 << 17 class-attribute

This user was one of the first to be verified

DISCORD_CERTIFIED_MODERATOR = 1 << 18 class-attribute

This user is a certified moderator

BOT_HTTP_INTERACTIONS = 1 << 19 class-attribute

Bot uses only HTTP interactions and is shown in the online member list

SPAMMER = 1 << 20 class-attribute

A user who is suspected of spamming

DISABLE_PREMIUM = 1 << 21 class-attribute

Nitro features disabled for this user. Only used by Discord Staff for testing

ApplicationFlags

Bases: DiscordIntFlag

Flags an application can have.

Source code in naff/models/discord/enums.py
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
class ApplicationFlags(DiscordIntFlag):  # type: ignore
    """Flags an application can have."""

    # Flags defined by the Discord API
    GATEWAY_PRESENCE = 1 << 12
    """Verified to use presence intent"""
    GATEWAY_PRESENCE_LIMITED = 1 << 13
    """Using presence intent, without verification"""
    GATEWAY_GUILD_MEMBERS = 1 << 14
    """Verified to use guild members intent"""
    GATEWAY_GUILD_MEMBERS_LIMITED = 1 << 15
    """Using members intent, without verification"""
    VERIFICATION_PENDING_GUILD_LIMIT = 1 << 16
    """Bot has hit guild limit, and has not been successfully verified"""
    EMBEDDED = 1 << 17
    """Application is a voice channel activity (ie YouTube Together)"""

GATEWAY_PRESENCE = 1 << 12 class-attribute

Verified to use presence intent

GATEWAY_PRESENCE_LIMITED = 1 << 13 class-attribute

Using presence intent, without verification

GATEWAY_GUILD_MEMBERS = 1 << 14 class-attribute

Verified to use guild members intent

GATEWAY_GUILD_MEMBERS_LIMITED = 1 << 15 class-attribute

Using members intent, without verification

VERIFICATION_PENDING_GUILD_LIMIT = 1 << 16 class-attribute

Bot has hit guild limit, and has not been successfully verified

EMBEDDED = 1 << 17 class-attribute

Application is a voice channel activity (ie YouTube Together)

TeamMembershipState

Bases: CursedIntEnum

Status of membership in the team.

Source code in naff/models/discord/enums.py
289
290
291
292
293
class TeamMembershipState(CursedIntEnum):
    """Status of membership in the team."""

    INVITED = 1
    ACCEPTED = 2

PremiumTypes

Bases: CursedIntEnum

Types of premium membership.

Source code in naff/models/discord/enums.py
296
297
298
299
300
301
302
303
304
class PremiumTypes(CursedIntEnum):
    """Types of premium membership."""

    NONE = 0
    """No premium membership"""
    NITRO_CLASSIC = 1
    """Using Nitro Classic"""
    NITRO = 2
    """Full Nitro membership"""

NONE = 0 class-attribute

No premium membership

NITRO_CLASSIC = 1 class-attribute

Using Nitro Classic

NITRO = 2 class-attribute

Full Nitro membership

MessageTypes

Bases: CursedIntEnum

Types of message.

Source code in naff/models/discord/enums.py
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
class MessageTypes(CursedIntEnum):
    """Types of message."""

    DEFAULT = 0
    RECIPIENT_ADD = 1
    RECIPIENT_REMOVE = 2
    CALL = 3
    CHANNEL_NAME_CHANGE = 4
    CHANNEL_ICON_CHANGE = 5
    CHANNEL_PINNED_MESSAGE = 6
    GUILD_MEMBER_JOIN = 7
    USER_PREMIUM_GUILD_SUBSCRIPTION = 8
    USER_PREMIUM_GUILD_SUBSCRIPTION_TIER_1 = 9
    USER_PREMIUM_GUILD_SUBSCRIPTION_TIER_2 = 10
    USER_PREMIUM_GUILD_SUBSCRIPTION_TIER_3 = 11
    CHANNEL_FOLLOW_ADD = 12
    GUILD_DISCOVERY_DISQUALIFIED = 14
    GUILD_DISCOVERY_REQUALIFIED = 15
    GUILD_DISCOVERY_GRACE_PERIOD_INITIAL_WARNING = 16
    GUILD_DISCOVERY_GRACE_PERIOD_FINAL_WARNING = 17
    THREAD_CREATED = 18
    REPLY = 19
    APPLICATION_COMMAND = 20
    THREAD_STARTER_MESSAGE = 21
    GUILD_INVITE_REMINDER = 22
    CONTEXT_MENU_COMMAND = 23
    AUTO_MODERATION_ACTION = 24

EmbedTypes

Bases: Enum

Types of embed.

Source code in naff/models/discord/enums.py
336
337
338
339
340
341
342
343
344
345
class EmbedTypes(Enum):
    """Types of embed."""

    RICH = "rich"
    IMAGE = "image"
    VIDEO = "video"
    GIFV = "gifv"
    ARTICLE = "article"
    LINK = "link"
    AUTOMOD_MESSAGE = "auto_moderation_message"

MessageActivityTypes

Bases: CursedIntEnum

An activity object, similar to an embed.

Source code in naff/models/discord/enums.py
348
349
350
351
352
353
354
355
356
357
358
class MessageActivityTypes(CursedIntEnum):
    """An activity object, similar to an embed."""

    JOIN = 1
    """Join the event"""
    SPECTATE = 2
    """Watch the event"""
    LISTEN = 3
    """Listen along to the event"""
    JOIN_REQUEST = 5
    """Asking a user to join the activity"""

JOIN = 1 class-attribute

Join the event

SPECTATE = 2 class-attribute

Watch the event

LISTEN = 3 class-attribute

Listen along to the event

JOIN_REQUEST = 5 class-attribute

Asking a user to join the activity

MessageFlags

Bases: DiscordIntFlag

Message flags.

Source code in naff/models/discord/enums.py
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
class MessageFlags(DiscordIntFlag):  # type: ignore
    """Message flags."""

    CROSSPOSTED = 1 << 0
    """This message has been published to subscribed channels (via Channel Following)"""
    IS_CROSSPOST = 1 << 1
    """This message originated from a message in another channel (via Channel Following)"""
    SUPPRESS_EMBEDS = 1 << 2
    """Do not include any embeds when serializing this message"""
    SOURCE_MESSAGE_DELETED = 1 << 3
    """The source message for this crosspost has been deleted (via Channel Following)"""
    URGENT = 1 << 4
    """This message came from the urgent message system"""
    HAS_THREAD = 1 << 5
    """This message has an associated thread, with the same id as the message"""
    EPHEMERAL = 1 << 6
    """This message is only visible to the user who invoked the Interaction"""
    LOADING = 1 << 7
    """This message is an Interaction Response and the bot is "thinking"""
    FAILED_TO_MENTION_SOME_ROLES_IN_THREAD = 1 << 8
    """This message failed to mention some roles and add their members to the thread"""
    SHOULD_SHOW_LINK_NOT_DISCORD_WARNING = 1 << 10
    """This message contains a abusive website link, pops up a warning when clicked"""

    # Special members
    NONE = 0
    ALL = AntiFlag()

CROSSPOSTED = 1 << 0 class-attribute

This message has been published to subscribed channels (via Channel Following)

IS_CROSSPOST = 1 << 1 class-attribute

This message originated from a message in another channel (via Channel Following)

SUPPRESS_EMBEDS = 1 << 2 class-attribute

Do not include any embeds when serializing this message

SOURCE_MESSAGE_DELETED = 1 << 3 class-attribute

The source message for this crosspost has been deleted (via Channel Following)

URGENT = 1 << 4 class-attribute

This message came from the urgent message system

HAS_THREAD = 1 << 5 class-attribute

This message has an associated thread, with the same id as the message

EPHEMERAL = 1 << 6 class-attribute

This message is only visible to the user who invoked the Interaction

LOADING = 1 << 7 class-attribute

This message is an Interaction Response and the bot is "thinking

FAILED_TO_MENTION_SOME_ROLES_IN_THREAD = 1 << 8 class-attribute

This message failed to mention some roles and add their members to the thread

This message contains a abusive website link, pops up a warning when clicked

Permissions

Bases: DiscordIntFlag

Permissions a user or role may have.

Source code in naff/models/discord/enums.py
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
class Permissions(DiscordIntFlag):  # type: ignore
    """Permissions a user or role may have."""

    # Permissions defined by Discord API
    CREATE_INSTANT_INVITE = 1 << 0
    """Allows creation of instant invites"""
    KICK_MEMBERS = 1 << 1
    """Allows kicking members"""
    BAN_MEMBERS = 1 << 2
    """Allows banning members"""
    ADMINISTRATOR = 1 << 3
    """Allows all permissions and bypasses channel permission overwrites"""
    MANAGE_CHANNELS = 1 << 4
    """Allows management and editing of channels"""
    MANAGE_GUILD = 1 << 5
    """Allows management and editing of the guild"""
    ADD_REACTIONS = 1 << 6
    """Allows for the addition of reactions to messages"""
    VIEW_AUDIT_LOG = 1 << 7
    """Allows for viewing of audit logs"""
    PRIORITY_SPEAKER = 1 << 8
    """Allows for using priority speaker in a voice channel"""
    STREAM = 1 << 9
    """Allows the user to go live"""
    VIEW_CHANNEL = 1 << 10
    """Allows guild members to view a channel, which includes reading messages in text channels and joining voice channels"""
    SEND_MESSAGES = 1 << 11
    """	Allows for sending messages in a channel (does not allow sending messages in threads)"""
    CREATE_POSTS = 1 << 11
    """Allow members to create posts in this channel. Alias to SEND_MESSAGES"""
    SEND_TTS_MESSAGES = 1 << 12
    """	Allows for sending of `/tts` messages"""
    MANAGE_MESSAGES = 1 << 13
    """Allows for deletion of other users messages"""
    EMBED_LINKS = 1 << 14
    """Links sent by users with this permission will be auto-embedded"""
    ATTACH_FILES = 1 << 15
    """Allows for uploading images and files"""
    READ_MESSAGE_HISTORY = 1 << 16
    """Allows for reading of message history"""
    MENTION_EVERYONE = 1 << 17
    """Allows for using the `@everyone` tag to notify all users in a channel, and the `@here` tag to notify all online users in a channel"""
    USE_EXTERNAL_EMOJIS = 1 << 18
    """Allows the usage of custom emojis from other servers"""
    VIEW_GUILD_INSIGHTS = 1 << 19
    """Allows for viewing guild insights"""
    CONNECT = 1 << 20
    """Allows for joining of a voice channel"""
    SPEAK = 1 << 21
    """Allows for speaking in a voice channel"""
    MUTE_MEMBERS = 1 << 22
    """Allows for muting members in a voice channel"""
    DEAFEN_MEMBERS = 1 << 23
    """Allows for deafening of members in a voice channel"""
    MOVE_MEMBERS = 1 << 24
    """Allows for moving of members between voice channels"""
    USE_VAD = 1 << 25
    """Allows for using voice-activity-detection in a voice channel"""
    CHANGE_NICKNAME = 1 << 26
    """Allows for modification of own nickname"""
    MANAGE_NICKNAMES = 1 << 27
    """Allows for modification of other users nicknames"""
    MANAGE_ROLES = 1 << 28
    """Allows management and editing of roles"""
    MANAGE_WEBHOOKS = 1 << 29
    """Allows management and editing of webhooks"""
    MANAGE_EMOJIS_AND_STICKERS = 1 << 30
    """Allows management and editing of emojis and stickers"""
    USE_APPLICATION_COMMANDS = 1 << 31
    """Allows members to use application commands, including slash commands and context menu commands"""
    REQUEST_TO_SPEAK = 1 << 32
    """Allows for requesting to speak in stage channels. (This permission is under active development and may be changed or removed.)"""
    MANAGE_EVENTS = 1 << 33
    """Allows for creating, editing, and deleting scheduled events"""
    MANAGE_THREADS = 1 << 34
    """Allows for deleting and archiving threads, and viewing all private threads"""
    USE_PUBLIC_THREADS = 1 << 35
    """	Allows for creating public and announcement threads"""
    USE_PRIVATE_THREADS = 1 << 36
    """Allows for creating private threads"""
    USE_EXTERNAL_STICKERS = 1 << 37
    """Allows the usage of custom stickers from other servers"""
    SEND_MESSAGES_IN_THREADS = 1 << 38
    """Allows for sending messages in threads"""
    START_EMBEDDED_ACTIVITIES = 1 << 39
    """Allows for using Activities (applications with the `EMBEDDED` flag) in a voice channel"""
    MODERATE_MEMBERS = 1 << 40
    """Allows for timing out users to prevent them from sending or reacting to messages in chat and threads, and from speaking in voice and stage channels"""

    # Shortcuts/grouping/aliases
    REQUIRES_MFA = (
        KICK_MEMBERS
        | BAN_MEMBERS
        | ADMINISTRATOR
        | MANAGE_CHANNELS
        | MANAGE_GUILD
        | MANAGE_MESSAGES
        | MANAGE_ROLES
        | MANAGE_WEBHOOKS
        | MANAGE_EMOJIS_AND_STICKERS
        | MANAGE_THREADS
        | MODERATE_MEMBERS
    )
    USE_SLASH_COMMANDS = USE_APPLICATION_COMMANDS
    """Legacy alias for :attr:`USE_APPLICATION_COMMANDS`"""

    # Special members
    NONE = 0
    ALL = AntiFlag()

CREATE_INSTANT_INVITE = 1 << 0 class-attribute

Allows creation of instant invites

KICK_MEMBERS = 1 << 1 class-attribute

Allows kicking members

BAN_MEMBERS = 1 << 2 class-attribute

Allows banning members

ADMINISTRATOR = 1 << 3 class-attribute

Allows all permissions and bypasses channel permission overwrites

MANAGE_CHANNELS = 1 << 4 class-attribute

Allows management and editing of channels

MANAGE_GUILD = 1 << 5 class-attribute

Allows management and editing of the guild

ADD_REACTIONS = 1 << 6 class-attribute

Allows for the addition of reactions to messages

VIEW_AUDIT_LOG = 1 << 7 class-attribute

Allows for viewing of audit logs

PRIORITY_SPEAKER = 1 << 8 class-attribute

Allows for using priority speaker in a voice channel

STREAM = 1 << 9 class-attribute

Allows the user to go live

VIEW_CHANNEL = 1 << 10 class-attribute

Allows guild members to view a channel, which includes reading messages in text channels and joining voice channels

SEND_MESSAGES = 1 << 11 class-attribute

Allows for sending messages in a channel (does not allow sending messages in threads)

CREATE_POSTS = 1 << 11 class-attribute

Allow members to create posts in this channel. Alias to SEND_MESSAGES

SEND_TTS_MESSAGES = 1 << 12 class-attribute

Allows for sending of /tts messages

MANAGE_MESSAGES = 1 << 13 class-attribute

Allows for deletion of other users messages

Links sent by users with this permission will be auto-embedded

ATTACH_FILES = 1 << 15 class-attribute

Allows for uploading images and files

READ_MESSAGE_HISTORY = 1 << 16 class-attribute

Allows for reading of message history

MENTION_EVERYONE = 1 << 17 class-attribute

Allows for using the @everyone tag to notify all users in a channel, and the @here tag to notify all online users in a channel

USE_EXTERNAL_EMOJIS = 1 << 18 class-attribute

Allows the usage of custom emojis from other servers

VIEW_GUILD_INSIGHTS = 1 << 19 class-attribute

Allows for viewing guild insights

CONNECT = 1 << 20 class-attribute

Allows for joining of a voice channel

SPEAK = 1 << 21 class-attribute

Allows for speaking in a voice channel

MUTE_MEMBERS = 1 << 22 class-attribute

Allows for muting members in a voice channel

DEAFEN_MEMBERS = 1 << 23 class-attribute

Allows for deafening of members in a voice channel

MOVE_MEMBERS = 1 << 24 class-attribute

Allows for moving of members between voice channels

USE_VAD = 1 << 25 class-attribute

Allows for using voice-activity-detection in a voice channel

CHANGE_NICKNAME = 1 << 26 class-attribute

Allows for modification of own nickname

MANAGE_NICKNAMES = 1 << 27 class-attribute

Allows for modification of other users nicknames

MANAGE_ROLES = 1 << 28 class-attribute

Allows management and editing of roles

MANAGE_WEBHOOKS = 1 << 29 class-attribute

Allows management and editing of webhooks

MANAGE_EMOJIS_AND_STICKERS = 1 << 30 class-attribute

Allows management and editing of emojis and stickers

USE_APPLICATION_COMMANDS = 1 << 31 class-attribute

Allows members to use application commands, including slash commands and context menu commands

REQUEST_TO_SPEAK = 1 << 32 class-attribute

Allows for requesting to speak in stage channels. (This permission is under active development and may be changed or removed.)

MANAGE_EVENTS = 1 << 33 class-attribute

Allows for creating, editing, and deleting scheduled events

MANAGE_THREADS = 1 << 34 class-attribute

Allows for deleting and archiving threads, and viewing all private threads

USE_PUBLIC_THREADS = 1 << 35 class-attribute

Allows for creating public and announcement threads

USE_PRIVATE_THREADS = 1 << 36 class-attribute

Allows for creating private threads

USE_EXTERNAL_STICKERS = 1 << 37 class-attribute

Allows the usage of custom stickers from other servers

SEND_MESSAGES_IN_THREADS = 1 << 38 class-attribute

Allows for sending messages in threads

START_EMBEDDED_ACTIVITIES = 1 << 39 class-attribute

Allows for using Activities (applications with the EMBEDDED flag) in a voice channel

MODERATE_MEMBERS = 1 << 40 class-attribute

Allows for timing out users to prevent them from sending or reacting to messages in chat and threads, and from speaking in voice and stage channels

USE_SLASH_COMMANDS = USE_APPLICATION_COMMANDS class-attribute

Legacy alias for :attr:USE_APPLICATION_COMMANDS

ChannelTypes

Bases: CursedIntEnum

Types of channel.

Source code in naff/models/discord/enums.py
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
class ChannelTypes(CursedIntEnum):
    """Types of channel."""

    GUILD_TEXT = 0
    """Text channel within a server"""
    DM = 1
    """Direct message between users"""
    GUILD_VOICE = 2
    """Voice channel within a server"""
    GROUP_DM = 3
    """Direct message between multiple users"""
    GUILD_CATEGORY = 4
    """Organizational category that contains up to 50 channels"""
    GUILD_NEWS = 5
    """Channel that users can follow and crosspost into their own server"""
    GUILD_NEWS_THREAD = 10
    """Temporary sub-channel within a GUILD_NEWS channel"""
    GUILD_PUBLIC_THREAD = 11
    """Temporary sub-channel within a GUILD_TEXT channel"""
    GUILD_PRIVATE_THREAD = 12
    """Temporary sub-channel within a GUILD_TEXT channel that is only viewable by those invited and those with the MANAGE_THREADS permission"""
    GUILD_STAGE_VOICE = 13
    """Voice channel for hosting events with an audience"""
    GUILD_FORUM = 15
    """A Forum channel"""

    @property
    def guild(self) -> bool:
        """Whether this channel is a guild channel."""
        return self.value not in {1, 3}

    @property
    def voice(self) -> bool:
        """Whether this channel is a voice channel."""
        return self.value in {2, 13}

GUILD_TEXT = 0 class-attribute

Text channel within a server

DM = 1 class-attribute

Direct message between users

GUILD_VOICE = 2 class-attribute

Voice channel within a server

GROUP_DM = 3 class-attribute

Direct message between multiple users

GUILD_CATEGORY = 4 class-attribute

Organizational category that contains up to 50 channels

GUILD_NEWS = 5 class-attribute

Channel that users can follow and crosspost into their own server

GUILD_NEWS_THREAD = 10 class-attribute

Temporary sub-channel within a GUILD_NEWS channel

GUILD_PUBLIC_THREAD = 11 class-attribute

Temporary sub-channel within a GUILD_TEXT channel

GUILD_PRIVATE_THREAD = 12 class-attribute

Temporary sub-channel within a GUILD_TEXT channel that is only viewable by those invited and those with the MANAGE_THREADS permission

GUILD_STAGE_VOICE = 13 class-attribute

Voice channel for hosting events with an audience

GUILD_FORUM = 15 class-attribute

A Forum channel

guild() property

Whether this channel is a guild channel.

Source code in naff/models/discord/enums.py
527
528
529
530
@property
def guild(self) -> bool:
    """Whether this channel is a guild channel."""
    return self.value not in {1, 3}

voice() property

Whether this channel is a voice channel.

Source code in naff/models/discord/enums.py
532
533
534
535
@property
def voice(self) -> bool:
    """Whether this channel is a voice channel."""
    return self.value in {2, 13}

ComponentTypes

Bases: CursedIntEnum

The types of components supported by discord.

Source code in naff/models/discord/enums.py
538
539
540
541
542
543
544
545
546
547
548
class ComponentTypes(CursedIntEnum):
    """The types of components supported by discord."""

    ACTION_ROW = 1
    """Container for other components"""
    BUTTON = 2
    """Button object"""
    SELECT = 3
    """Select menu for picking from choices"""
    INPUT_TEXT = 4
    """Text input object"""

ACTION_ROW = 1 class-attribute

Container for other components

BUTTON = 2 class-attribute

Button object

SELECT = 3 class-attribute

Select menu for picking from choices

INPUT_TEXT = 4 class-attribute

Text input object

CommandTypes

Bases: CursedIntEnum

The interaction commands supported by discord.

Source code in naff/models/discord/enums.py
551
552
553
554
555
556
557
558
559
class CommandTypes(CursedIntEnum):
    """The interaction commands supported by discord."""

    CHAT_INPUT = 1
    """Slash commands; a text-based command that shows up when a user types `/`"""
    USER = 2
    """A UI-based command that shows up when you right click or tap on a user"""
    MESSAGE = 3
    """A UI-based command that shows up when you right click or tap on a message"""

CHAT_INPUT = 1 class-attribute

Slash commands; a text-based command that shows up when a user types /

USER = 2 class-attribute

A UI-based command that shows up when you right click or tap on a user

MESSAGE = 3 class-attribute

A UI-based command that shows up when you right click or tap on a message

InteractionTypes

Bases: CursedIntEnum

The type of interaction received by discord.

Source code in naff/models/discord/enums.py
562
563
564
565
566
567
568
569
class InteractionTypes(CursedIntEnum):
    """The type of interaction received by discord."""

    PING = 1
    APPLICATION_COMMAND = 2
    MESSAGE_COMPONENT = 3
    AUTOCOMPLETE = 4
    MODAL_RESPONSE = 5

ButtonStyles

Bases: CursedIntEnum

The styles of buttons supported.

Source code in naff/models/discord/enums.py
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
class ButtonStyles(CursedIntEnum):
    """The styles of buttons supported."""

    # Based on discord api
    PRIMARY = 1
    """blurple"""
    SECONDARY = 2
    """grey"""
    SUCCESS = 3
    """green"""
    DANGER = 4
    """red"""
    LINK = 5
    """url button"""

    # Aliases
    BLUE = 1
    BLURPLE = 1
    GRAY = 2
    GREY = 2
    GREEN = 3
    RED = 4
    URL = 5

PRIMARY = 1 class-attribute

blurple

SECONDARY = 2 class-attribute

grey

SUCCESS = 3 class-attribute

green

DANGER = 4 class-attribute

red

url button

MentionTypes

Bases: str, Enum

Types of mention.

Source code in naff/models/discord/enums.py
597
598
599
600
601
602
class MentionTypes(str, Enum):
    """Types of mention."""

    EVERYONE = "everyone"
    ROLES = "roles"
    USERS = "users"

OverwriteTypes

Bases: CursedIntEnum

Types of permission overwrite.

Source code in naff/models/discord/enums.py
605
606
607
608
609
class OverwriteTypes(CursedIntEnum):
    """Types of permission overwrite."""

    ROLE = 0
    MEMBER = 1

DefaultNotificationLevels

Bases: CursedIntEnum

Default Notification levels for dms and guilds.

Source code in naff/models/discord/enums.py
612
613
614
615
616
class DefaultNotificationLevels(CursedIntEnum):
    """Default Notification levels for dms and guilds."""

    ALL_MESSAGES = 0
    ONLY_MENTIONS = 1

ExplicitContentFilterLevels

Bases: CursedIntEnum

Automatic filtering of explicit content.

Source code in naff/models/discord/enums.py
619
620
621
622
623
624
class ExplicitContentFilterLevels(CursedIntEnum):
    """Automatic filtering of explicit content."""

    DISABLED = 0
    MEMBERS_WITHOUT_ROLES = 1
    ALL_MEMBERS = 2

MFALevels

Bases: CursedIntEnum

Does the user use 2FA.

Source code in naff/models/discord/enums.py
627
628
629
630
631
class MFALevels(CursedIntEnum):
    """Does the user use 2FA."""

    NONE = 0
    ELEVATED = 1

VerificationLevels

Bases: CursedIntEnum

Levels of verification needed by a guild.

Source code in naff/models/discord/enums.py
634
635
636
637
638
639
640
641
642
643
644
645
646
class VerificationLevels(CursedIntEnum):
    """Levels of verification needed by a guild."""

    NONE = 0
    """No verification needed"""
    LOW = 1
    """Must have a verified email on their Discord Account"""
    MEDIUM = 2
    """Must also be registered on Discord for longer than 5 minutes"""
    HIGH = 3
    """Must also be a member of this server for longer than 10 minutes"""
    VERY_HIGH = 4
    """Must have a verified phone number on their Discord Account"""

NONE = 0 class-attribute

No verification needed

LOW = 1 class-attribute

Must have a verified email on their Discord Account

MEDIUM = 2 class-attribute

Must also be registered on Discord for longer than 5 minutes

HIGH = 3 class-attribute

Must also be a member of this server for longer than 10 minutes

VERY_HIGH = 4 class-attribute

Must have a verified phone number on their Discord Account

NSFWLevels

Bases: CursedIntEnum

A guilds NSFW Level.

Source code in naff/models/discord/enums.py
649
650
651
652
653
654
655
class NSFWLevels(CursedIntEnum):
    """A guilds NSFW Level."""

    DEFAULT = 0
    EXPLICIT = 1
    SAFE = 2
    AGE_RESTRICTED = 3

PremiumTiers

Bases: CursedIntEnum

The boost level of a server.

Source code in naff/models/discord/enums.py
658
659
660
661
662
663
664
665
666
667
668
class PremiumTiers(CursedIntEnum):
    """The boost level of a server."""

    NONE = 0
    """Guild has not unlocked any Server Boost perks"""
    TIER_1 = 1
    """Guild has unlocked Tier 1 Server Boost perks"""
    TIER_2 = 2
    """Guild has unlocked Tier 2 Server Boost perks"""
    TIER_3 = 3
    """Guild has unlocked Tier 3 Server Boost perks"""

NONE = 0 class-attribute

Guild has not unlocked any Server Boost perks

TIER_1 = 1 class-attribute

Guild has unlocked Tier 1 Server Boost perks

TIER_2 = 2 class-attribute

Guild has unlocked Tier 2 Server Boost perks

TIER_3 = 3 class-attribute

Guild has unlocked Tier 3 Server Boost perks

SystemChannelFlags

Bases: DiscordIntFlag

System channel settings.

Source code in naff/models/discord/enums.py
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
class SystemChannelFlags(DiscordIntFlag):
    """System channel settings."""

    SUPPRESS_JOIN_NOTIFICATIONS = 1 << 0
    """Suppress member join notifications"""
    SUPPRESS_PREMIUM_SUBSCRIPTIONS = 1 << 1
    """Suppress server boost notifications"""
    SUPPRESS_GUILD_REMINDER_NOTIFICATIONS = 1 << 2
    """Suppress server setup tips"""
    SUPPRESS_JOIN_NOTIFICATION_REPLIES = 1 << 3
    """Hide member join sticker reply buttons"""

    # Special members
    NONE = 0
    ALL = AntiFlag()

SUPPRESS_JOIN_NOTIFICATIONS = 1 << 0 class-attribute

Suppress member join notifications

SUPPRESS_PREMIUM_SUBSCRIPTIONS = 1 << 1 class-attribute

Suppress server boost notifications

SUPPRESS_GUILD_REMINDER_NOTIFICATIONS = 1 << 2 class-attribute

Suppress server setup tips

SUPPRESS_JOIN_NOTIFICATION_REPLIES = 1 << 3 class-attribute

Hide member join sticker reply buttons

ChannelFlags

Bases: DiscordIntFlag

Source code in naff/models/discord/enums.py
688
689
690
691
692
693
class ChannelFlags(DiscordIntFlag):
    PINNED = 1 << 1
    """ Thread is pinned to the top of its parent forum channel """

    # Special members
    NONE = 0

PINNED = 1 << 1 class-attribute

Thread is pinned to the top of its parent forum channel

VideoQualityModes

Bases: CursedIntEnum

Video quality settings.

Source code in naff/models/discord/enums.py
696
697
698
699
700
class VideoQualityModes(CursedIntEnum):
    """Video quality settings."""

    AUTO = 1
    FULL = 2

AutoArchiveDuration

Bases: CursedIntEnum

Thread archive duration, in minutes.

Source code in naff/models/discord/enums.py
703
704
705
706
707
708
709
class AutoArchiveDuration(CursedIntEnum):
    """Thread archive duration, in minutes."""

    ONE_HOUR = 60
    ONE_DAY = 1440
    THREE_DAY = 4320
    ONE_WEEK = 10080

ActivityType

Bases: CursedIntEnum

The types of presence activity that can be used in presences.

Note

Only GAME STREAMING LISTENING WATCHING and COMPETING are usable by bots

Source code in naff/models/discord/enums.py
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
class ActivityType(CursedIntEnum):
    """
    The types of presence activity that can be used in presences.

    !!! note
        Only `GAME` `STREAMING` `LISTENING` `WATCHING` and `COMPETING` are usable by bots

    """

    GAME = 0
    """Playing {name}; Example: Playing Rocket League"""
    STREAMING = 1
    """Streaming {details}; Example: Streaming Rocket League"""
    LISTENING = 2
    """Listening to {name}; Example: Listening to Spotify"""
    WATCHING = 3
    """Watching {name}; Example: Watching YouTube Together"""
    CUSTOM = 4
    """{emoji} {name}; Example: `:smiley: I am cool`"""
    COMPETING = 5
    """Competing in {name}; Example: Competing in Arena World Champions"""

    PLAYING = GAME
    """Alias for `GAME`"""

GAME = 0 class-attribute

Playing {name}; Example: Playing Rocket League

STREAMING = 1 class-attribute

Streaming {details}; Example: Streaming Rocket League

LISTENING = 2 class-attribute

Listening to {name}; Example: Listening to Spotify

WATCHING = 3 class-attribute

Watching {name}; Example: Watching YouTube Together

CUSTOM = 4 class-attribute

{emoji} {name}; Example: :smiley: I am cool

COMPETING = 5 class-attribute

Competing in {name}; Example: Competing in Arena World Champions

PLAYING = GAME class-attribute

Alias for GAME

Status

Bases: str, Enum

Represents the statuses a user may have.

Source code in naff/models/discord/enums.py
750
751
752
753
754
755
756
757
758
759
760
class Status(str, Enum):
    """Represents the statuses a user may have."""

    ONLINE = "online"
    OFFLINE = "offline"
    DND = "dnd"
    IDLE = "idle"
    INVISIBLE = "invisible"

    AFK = IDLE
    DO_NOT_DISTURB = DND

ScheduledEventPrivacyLevel

Bases: CursedIntEnum

The privacy level of the scheduled event.

Source code in naff/models/discord/enums.py
778
779
780
781
class ScheduledEventPrivacyLevel(CursedIntEnum):
    """The privacy level of the scheduled event."""

    GUILD_ONLY = 2

ScheduledEventType

Bases: CursedIntEnum

The type of entity that the scheduled event is attached to.

Source code in naff/models/discord/enums.py
784
785
786
787
788
789
790
791
792
class ScheduledEventType(CursedIntEnum):
    """The type of entity that the scheduled event is attached to."""

    STAGE_INSTANCE = 1
    """ Stage Channel """
    VOICE = 2
    """ Voice Channel """
    EXTERNAL = 3
    """ External URL """

STAGE_INSTANCE = 1 class-attribute

Stage Channel

VOICE = 2 class-attribute

Voice Channel

EXTERNAL = 3 class-attribute

External URL

ScheduledEventStatus

Bases: CursedIntEnum

The status of the scheduled event.

Source code in naff/models/discord/enums.py
795
796
797
798
799
800
801
class ScheduledEventStatus(CursedIntEnum):
    """The status of the scheduled event."""

    SCHEDULED = 1
    ACTIVE = 2
    COMPLETED = 3
    CANCELED = 4

AuditLogEventType

Bases: CursedIntEnum

The type of audit log entry type

Source code in naff/models/discord/enums.py
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
class AuditLogEventType(CursedIntEnum):
    """The type of audit log entry type"""

    GUILD_UPDATE = 1
    CHANNEL_CREATE = 10
    CHANNEL_UPDATE = 11
    CHANNEL_DELETE = 12
    CHANNEL_OVERWRITE_CREATE = 13
    CHANNEL_OVERWRITE_UPDATE = 14
    CHANNEL_OVERWRITE_DELETE = 15
    MEMBER_KICK = 20
    MEMBER_PRUNE = 21
    MEMBER_BAN_ADD = 22
    MEMBER_BAN_REMOVE = 23
    MEMBER_UPDATE = 24
    MEMBER_ROLE_UPDATE = 25
    MEMBER_MOVE = 26
    MEMBER_DISCONNECT = 27
    BOT_ADD = 28
    ROLE_CREATE = 30
    ROLE_UPDATE = 31
    ROLE_DELETE = 32
    INVITE_CREATE = 40
    INVITE_UPDATE = 41
    INVITE_DELETE = 42
    WEBHOOK_CREATE = 50
    WEBHOOK_UPDATE = 51
    WEBHOOK_DELETE = 52
    EMOJI_CREATE = 60
    EMOJI_UPDATE = 61
    EMOJI_DELETE = 62
    MESSAGE_DELETE = 72
    MESSAGE_BULK_DELETE = 73
    MESSAGE_PIN = 74
    MESSAGE_UNPIN = 75
    INTEGRATION_CREATE = 80
    INTEGRATION_UPDATE = 81
    INTEGRATION_DELETE = 82
    STAGE_INSTANCE_CREATE = 83
    STAGE_INSTANCE_UPDATE = 84
    STAGE_INSTANCE_DELETE = 85
    STICKER_CREATE = 90
    STICKER_UPDATE = 91
    STICKER_DELETE = 92
    GUILD_SCHEDULED_EVENT_CREATE = 100
    GUILD_SCHEDULED_EVENT_UPDATE = 101
    GUILD_SCHEDULED_EVENT_DELETE = 102
    THREAD_CREATE = 110
    THREAD_UPDATE = 111
    THREAD_DELETE = 112
    APPLICATION_COMMAND_PERMISSION_UPDATE = 121
    AUTO_MODERATION_RULE_CREATE = 140
    AUTO_MODERATION_RULE_UPDATE = 141
    AUTO_MODERATION_RULE_DELETE = 142
    AUTO_MODERATION_BLOCK_MESSAGE = 143
    CREATOR_MONETIZATION_REQUEST_CREATED = 150
    CREATOR_MONETIZATION_TERMS_ACCEPTED = 151
    ROLE_PROMPT_CREATE = 160
    ROLE_PROMPT_UPDATE = 161
    ROLE_PROMPT_DELETE = 162
    GUILD_HOME_FEATURE_ITEM = 171
    GUILD_HOME_FEATURE_ITEM_UPDATE = 172

Timestamp

Bases: datetime

A special class that represents Discord timestamps.

Assumes that all naive datetimes are based on local timezone.

Source code in naff/models/discord/timestamp.py
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
class Timestamp(datetime):
    """
    A special class that represents Discord timestamps.

    Assumes that all naive datetimes are based on local timezone.

    """

    @classmethod
    def fromdatetime(cls, dt: datetime) -> "Timestamp":
        """Construct a timezone-aware UTC datetime from a datetime object."""
        timestamp = cls.fromtimestamp(dt.timestamp(), tz=dt.tzinfo)

        if timestamp.tzinfo is None:  # assume naive datetimes are based on local timezone
            return timestamp.astimezone()
        return timestamp

    @classmethod
    def utcfromtimestamp(cls, t: float) -> "Timestamp":
        """Construct a timezone-aware UTC datetime from a POSIX timestamp."""
        return super().utcfromtimestamp(t).replace(tzinfo=timezone.utc)

    @classmethod
    def fromisoformat(cls, date_string: str) -> "Timestamp":
        timestamp = super().fromisoformat(date_string)

        if timestamp.tzinfo is None:  # assume naive datetimes are based on local timezone
            return timestamp.astimezone()
        return timestamp

    @classmethod
    def fromisocalendar(cls, year: int, week: int, day: int) -> "Timestamp":
        return super().fromisocalendar(year, week, day).astimezone()

    @classmethod
    def fromtimestamp(cls, t: float, tz=None) -> "Timestamp":
        try:
            timestamp = super().fromtimestamp(t, tz=tz)
        except Exception:
            # May be in milliseconds instead of seconds
            timestamp = super().fromtimestamp(t / 1000, tz=tz)

        if timestamp.tzinfo is None:  # assume naive datetimes are based on local timezone
            return timestamp.astimezone()
        return timestamp

    @classmethod
    def fromordinal(cls, n: int) -> "Timestamp":
        return super().fromordinal(n).astimezone()

    @classmethod
    def now(cls, tz=None) -> "Timestamp":
        """
        Construct a datetime from time.time() and optional time zone info.

        If no timezone is provided, the time is assumed to be from the computer's
        local timezone.
        """
        t = time.time()
        return cls.fromtimestamp(t, tz)

    @classmethod
    def utcnow(cls) -> "Timestamp":
        """Construct a timezone-aware UTC datetime from time.time()."""
        t = time.time()
        return cls.utcfromtimestamp(t)

    def to_snowflake(self, high: bool = False) -> Union[str, int]:
        """
        Returns a numeric snowflake pretending to be created at the given date.

        When using as the lower end of a range, use ``tosnowflake(high=False) - 1``
        to be inclusive, ``high=True`` to be exclusive.
        When using as the higher end of a range, use ``tosnowflake(high=True) + 1``
        to be inclusive, ``high=False`` to be exclusive

        """
        discord_millis = int(self.timestamp() * 1000 - DISCORD_EPOCH)
        return (discord_millis << 22) + (2**22 - 1 if high else 0)

    @classmethod
    def from_snowflake(cls, snowflake: "Snowflake_Type") -> "Timestamp":
        """
        Construct a timezone-aware UTC datetime from a snowflake.

        Args:
            snowflake: The snowflake to convert.

        Returns:
            A timezone-aware UTC datetime.

        ??? Info
            https://discord.com/developers/docs/reference#convert-snowflake-to-datetime

        """
        if isinstance(snowflake, str):
            snowflake = int(snowflake)

        timestamp = ((snowflake >> 22) + DISCORD_EPOCH) / 1000
        return cls.utcfromtimestamp(timestamp)

    def format(self, style: Optional[Union[TimestampStyles, str]] = None) -> str:
        """
        Format the timestamp for discord client to display.

        Args:
            style: The style to format the timestamp with.

        Returns:
            The formatted timestamp.

        """
        if not style:
            return f"<t:{self.timestamp():.0f}>"
        else:
            return f"<t:{self.timestamp():.0f}:{style}>"

    def __str__(self) -> str:
        return self.format()

fromdatetime(dt) classmethod

Construct a timezone-aware UTC datetime from a datetime object.

Source code in naff/models/discord/timestamp.py
32
33
34
35
36
37
38
39
@classmethod
def fromdatetime(cls, dt: datetime) -> "Timestamp":
    """Construct a timezone-aware UTC datetime from a datetime object."""
    timestamp = cls.fromtimestamp(dt.timestamp(), tz=dt.tzinfo)

    if timestamp.tzinfo is None:  # assume naive datetimes are based on local timezone
        return timestamp.astimezone()
    return timestamp

utcfromtimestamp(t) classmethod

Construct a timezone-aware UTC datetime from a POSIX timestamp.

Source code in naff/models/discord/timestamp.py
41
42
43
44
@classmethod
def utcfromtimestamp(cls, t: float) -> "Timestamp":
    """Construct a timezone-aware UTC datetime from a POSIX timestamp."""
    return super().utcfromtimestamp(t).replace(tzinfo=timezone.utc)

now(tz=None) classmethod

Construct a datetime from time.time() and optional time zone info.

If no timezone is provided, the time is assumed to be from the computer's local timezone.

Source code in naff/models/discord/timestamp.py
74
75
76
77
78
79
80
81
82
83
@classmethod
def now(cls, tz=None) -> "Timestamp":
    """
    Construct a datetime from time.time() and optional time zone info.

    If no timezone is provided, the time is assumed to be from the computer's
    local timezone.
    """
    t = time.time()
    return cls.fromtimestamp(t, tz)

utcnow() classmethod

Construct a timezone-aware UTC datetime from time.time().

Source code in naff/models/discord/timestamp.py
85
86
87
88
89
@classmethod
def utcnow(cls) -> "Timestamp":
    """Construct a timezone-aware UTC datetime from time.time()."""
    t = time.time()
    return cls.utcfromtimestamp(t)

to_snowflake(high=False)

Returns a numeric snowflake pretending to be created at the given date.

When using as the lower end of a range, use tosnowflake(high=False) - 1 to be inclusive, high=True to be exclusive. When using as the higher end of a range, use tosnowflake(high=True) + 1 to be inclusive, high=False to be exclusive

Source code in naff/models/discord/timestamp.py
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
def to_snowflake(self, high: bool = False) -> Union[str, int]:
    """
    Returns a numeric snowflake pretending to be created at the given date.

    When using as the lower end of a range, use ``tosnowflake(high=False) - 1``
    to be inclusive, ``high=True`` to be exclusive.
    When using as the higher end of a range, use ``tosnowflake(high=True) + 1``
    to be inclusive, ``high=False`` to be exclusive

    """
    discord_millis = int(self.timestamp() * 1000 - DISCORD_EPOCH)
    return (discord_millis << 22) + (2**22 - 1 if high else 0)

from_snowflake(snowflake) classmethod

Construct a timezone-aware UTC datetime from a snowflake.

Parameters:

Name Type Description Default
snowflake Snowflake_Type

The snowflake to convert.

required

Returns:

Type Description
Timestamp

A timezone-aware UTC datetime.

Info

https://discord.com/developers/docs/reference#convert-snowflake-to-datetime

Source code in naff/models/discord/timestamp.py
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
@classmethod
def from_snowflake(cls, snowflake: "Snowflake_Type") -> "Timestamp":
    """
    Construct a timezone-aware UTC datetime from a snowflake.

    Args:
        snowflake: The snowflake to convert.

    Returns:
        A timezone-aware UTC datetime.

    ??? Info
        https://discord.com/developers/docs/reference#convert-snowflake-to-datetime

    """
    if isinstance(snowflake, str):
        snowflake = int(snowflake)

    timestamp = ((snowflake >> 22) + DISCORD_EPOCH) / 1000
    return cls.utcfromtimestamp(timestamp)

format(style=None)

Format the timestamp for discord client to display.

Parameters:

Name Type Description Default
style Optional[Union[TimestampStyles, str]]

The style to format the timestamp with.

None

Returns:

Type Description
str

The formatted timestamp.

Source code in naff/models/discord/timestamp.py
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
def format(self, style: Optional[Union[TimestampStyles, str]] = None) -> str:
    """
    Format the timestamp for discord client to display.

    Args:
        style: The style to format the timestamp with.

    Returns:
        The formatted timestamp.

    """
    if not style:
        return f"<t:{self.timestamp():.0f}>"
    else:
        return f"<t:{self.timestamp():.0f}:{style}>"

naff Models

Extension

A class that allows you to separate your commands and listeners into separate files. Skins require an entrypoint in the same file called setup, this function allows client to load the Extension.

Example Usage:
1
2
3
4
5
6
7
class ExampleExt(Extension):
    def __init__(self, bot):
        print("Extension Created")

    @prefixed_command()
    async def some_command(self, context):
        await ctx.send(f"I was sent from a extension called {self.name}")

Attributes:

Name Type Description
bot Client

A reference to the client

name str

The name of this Extension (read-only)

description str

A description of this Extension

extension_checks str

A list of checks to be ran on any command in this extension

extension_prerun List

A list of coroutines to be run before any command in this extension

extension_postrun List

A list of coroutines to be run after any command in this extension

Source code in naff/models/naff/extension.py
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
class Extension:
    """
    A class that allows you to separate your commands and listeners into separate files. Skins require an entrypoint in the same file called `setup`, this function allows client to load the Extension.

    ??? Hint "Example Usage:"
        ```python
        class ExampleExt(Extension):
            def __init__(self, bot):
                print("Extension Created")

            @prefixed_command()
            async def some_command(self, context):
                await ctx.send(f"I was sent from a extension called {self.name}")
        ```

    Attributes:
        bot Client: A reference to the client
        name str: The name of this Extension (`read-only`)
        description str: A description of this Extension
        extension_checks str: A list of checks to be ran on any command in this extension
        extension_prerun List: A list of coroutines to be run before any command in this extension
        extension_postrun List: A list of coroutines to be run after any command in this extension

    """

    bot: "Client"
    name: str
    extension_name: str
    description: str
    extension_checks: List
    extension_prerun: List
    extension_postrun: List
    extension_error: Optional[Callable[..., Coroutine]]
    _commands: List
    _listeners: List
    auto_defer: "AutoDefer"

    def __new__(cls, bot: "Client", *args, **kwargs) -> "Extension":
        new_cls = super().__new__(cls)
        new_cls.bot = bot
        new_cls.name = cls.__name__
        new_cls.extension_checks = []
        new_cls.extension_prerun = []
        new_cls.extension_postrun = []
        new_cls.extension_error = None
        new_cls.auto_defer = MISSING

        new_cls.description = kwargs.get("Description", None)
        if not new_cls.description:
            new_cls.description = inspect.cleandoc(cls.__doc__) if cls.__doc__ else None

        # load commands from class
        new_cls._commands = []
        new_cls._listeners = []

        for _name, val in inspect.getmembers(
            new_cls, predicate=lambda x: isinstance(x, (naff.BaseCommand, naff.Listener, Task))
        ):
            if isinstance(val, naff.BaseCommand):
                val.extension = new_cls
                val = wrap_partial(val, new_cls)

                if not isinstance(val, naff.PrefixedCommand) or not val.is_subcommand:
                    # we do not want to add prefixed subcommands
                    new_cls._commands.append(val)

                    if isinstance(val, naff.ModalCommand):
                        bot.add_modal_callback(val)
                    elif isinstance(val, naff.ComponentCommand):
                        bot.add_component_callback(val)
                    elif isinstance(val, naff.HybridCommand):
                        bot.add_hybrid_command(val)
                    elif isinstance(val, naff.InteractionCommand):
                        bot.add_interaction(val)
                    else:
                        bot.add_prefixed_command(val)

            elif isinstance(val, naff.Listener):
                val = val.copy_with_binding(new_cls)
                bot.add_listener(val)
                new_cls.listeners.append(val)
            elif isinstance(val, Task):
                wrap_partial(val, new_cls)

        logger.debug(
            f"{len(new_cls._commands)} commands and {len(new_cls.listeners)} listeners"
            f" have been loaded from `{new_cls.name}`"
        )

        new_cls.extension_name = inspect.getmodule(new_cls).__name__
        new_cls.bot.ext[new_cls.name] = new_cls

        if hasattr(new_cls, "async_start"):
            if inspect.iscoroutinefunction(new_cls.async_start):
                bot.async_startup_tasks.append(new_cls.async_start())
            else:
                raise TypeError("async_start is a reserved method and must be a coroutine")

        return new_cls

    @property
    def __name__(self) -> str:
        return self.name

    @property
    def commands(self) -> List["BaseCommand"]:
        """Get the commands from this Extension."""
        return self._commands

    @property
    def listeners(self) -> List["Listener"]:
        """Get the listeners from this Extension."""
        return self._listeners

    def drop(self) -> None:
        """Called when this Extension is being removed."""
        for func in self._commands:
            if isinstance(func, naff.ModalCommand):
                for listener in func.listeners:
                    # noinspection PyProtectedMember
                    self.bot._modal_callbacks.pop(listener)
            elif isinstance(func, naff.ComponentCommand):
                for listener in func.listeners:
                    # noinspection PyProtectedMember
                    self.bot._component_callbacks.pop(listener)
            elif isinstance(func, naff.InteractionCommand):
                for scope in func.scopes:
                    if self.bot.interactions.get(scope):
                        self.bot.interactions[scope].pop(func.resolved_name, [])

                if isinstance(func, naff.HybridCommand):
                    # here's where things get complicated - we need to unload the prefixed command
                    # by necessity, there's a lot of logic here to determine what needs to be unloaded
                    if not func.callback:  # not like it was added
                        return

                    if func.is_subcommand:
                        prefixed_base = self.bot.prefixed_commands.get(str(func.name))
                        _base_cmd = prefixed_base
                        if not prefixed_base:
                            # if something weird happened here, here's a safeguard
                            continue

                        if func.group_name:
                            prefixed_base = prefixed_base.subcommands.get(str(func.group_name))
                            if not prefixed_base:
                                continue

                        prefixed_base.remove_command(str(func.sub_cmd_name))

                        if not prefixed_base.subcommands:
                            # the base cmd is now empty, delete it
                            if func.group_name:
                                _base_cmd.remove_command(str(func.group_name))  # type: ignore

                                # and now the base command is empty
                                if not _base_cmd.subcommands:  # type: ignore
                                    # in case you're curious, i did try to put the below behavior
                                    # in a function here, but then it turns out a weird python
                                    # bug can happen if i did that
                                    if cmd := self.bot.prefixed_commands.pop(str(func.name), None):
                                        for alias in cmd.aliases:
                                            self.bot.prefixed_commands.pop(alias, None)

                            elif cmd := self.bot.prefixed_commands.pop(str(func.name), None):
                                for alias in cmd.aliases:
                                    self.bot.prefixed_commands.pop(alias, None)

                    elif cmd := self.bot.prefixed_commands.pop(str(func.name), None):
                        for alias in cmd.aliases:
                            self.bot.prefixed_commands.pop(alias, None)

            elif isinstance(func, naff.PrefixedCommand):
                if not func.is_subcommand:
                    self.bot.prefixed_commands.pop(func.name, None)
                    for alias in func.aliases:
                        self.bot.prefixed_commands.pop(alias, None)
        for func in self.listeners:
            self.bot.listeners[func.event].remove(func)

        self.bot.ext.pop(self.name, None)
        logger.debug(f"{self.name} has been drop")

    def add_ext_auto_defer(self, ephemeral: bool = False, time_until_defer: float = 0.0) -> None:
        """
        Add a auto defer for all commands in this extension.

        Args:
            ephemeral: Should the command be deferred as ephemeral
            time_until_defer: How long to wait before deferring automatically

        """
        self.auto_defer = naff.AutoDefer(enabled=True, ephemeral=ephemeral, time_until_defer=time_until_defer)

    def add_ext_check(self, coroutine: Callable[["Context"], Awaitable[bool]]) -> None:
        """
        Add a coroutine as a check for all commands in this extension to run. This coroutine must take **only** the parameter `context`.

        ??? Hint "Example Usage:"
            ```python
            def __init__(self, bot):
                self.add_ext_check(self.example)

            @staticmethod
            async def example(context: Context):
                if context.author.id == 123456789:
                    return True
                return False
            ```
        Args:
            coroutine: The coroutine to use as a check

        """
        if not asyncio.iscoroutinefunction(coroutine):
            raise TypeError("Check must be a coroutine")

        if not self.extension_checks:
            self.extension_checks = []

        self.extension_checks.append(coroutine)

    def add_extension_prerun(self, coroutine: Callable[..., Coroutine]) -> None:
        """
        Add a coroutine to be run **before** all commands in this Extension.

        !!! note
            Pre-runs will **only** be run if the commands checks pass

        ??? Hint "Example Usage:"
            ```python
            def __init__(self, bot):
                self.add_extension_prerun(self.example)

            async def example(self, context: Context):
                await ctx.send("I ran first")
            ```

        Args:
            coroutine: The coroutine to run

        """
        if not asyncio.iscoroutinefunction(coroutine):
            raise TypeError("Callback must be a coroutine")

        if not self.extension_prerun:
            self.extension_prerun = []
        self.extension_prerun.append(coroutine)

    def add_extension_postrun(self, coroutine: Callable[..., Coroutine]) -> None:
        """
        Add a coroutine to be run **after** all commands in this Extension.

        ??? Hint "Example Usage:"
            ```python
            def __init__(self, bot):
                self.add_extension_postrun(self.example)

            async def example(self, context: Context):
                await ctx.send("I ran first")
            ```

        Args:
            coroutine: The coroutine to run

        """
        if not asyncio.iscoroutinefunction(coroutine):
            raise TypeError("Callback must be a coroutine")

        if not self.extension_postrun:
            self.extension_postrun = []
        self.extension_postrun.append(coroutine)

    def set_extension_error(self, coroutine: Callable[..., Coroutine]) -> None:
        """
        Add a coroutine to handle any exceptions raised in this extension.

        ??? Hint "Example Usage:"
            ```python
            def __init__(self, bot):
                self.set_extension_error(self.example)

        Args:
            coroutine: The coroutine to run

        """
        if not asyncio.iscoroutinefunction(coroutine):
            raise TypeError("Callback must be a coroutine")

        if self.extension_error:
            logger.warning("Extension error callback has been overridden!")
        self.extension_error = coroutine

commands() property

Get the commands from this Extension.

Source code in naff/models/naff/extension.py
123
124
125
126
@property
def commands(self) -> List["BaseCommand"]:
    """Get the commands from this Extension."""
    return self._commands

listeners() property

Get the listeners from this Extension.

Source code in naff/models/naff/extension.py
128
129
130
131
@property
def listeners(self) -> List["Listener"]:
    """Get the listeners from this Extension."""
    return self._listeners

drop()

Called when this Extension is being removed.

Source code in naff/models/naff/extension.py
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
def drop(self) -> None:
    """Called when this Extension is being removed."""
    for func in self._commands:
        if isinstance(func, naff.ModalCommand):
            for listener in func.listeners:
                # noinspection PyProtectedMember
                self.bot._modal_callbacks.pop(listener)
        elif isinstance(func, naff.ComponentCommand):
            for listener in func.listeners:
                # noinspection PyProtectedMember
                self.bot._component_callbacks.pop(listener)
        elif isinstance(func, naff.InteractionCommand):
            for scope in func.scopes:
                if self.bot.interactions.get(scope):
                    self.bot.interactions[scope].pop(func.resolved_name, [])

            if isinstance(func, naff.HybridCommand):
                # here's where things get complicated - we need to unload the prefixed command
                # by necessity, there's a lot of logic here to determine what needs to be unloaded
                if not func.callback:  # not like it was added
                    return

                if func.is_subcommand:
                    prefixed_base = self.bot.prefixed_commands.get(str(func.name))
                    _base_cmd = prefixed_base
                    if not prefixed_base:
                        # if something weird happened here, here's a safeguard
                        continue

                    if func.group_name:
                        prefixed_base = prefixed_base.subcommands.get(str(func.group_name))
                        if not prefixed_base:
                            continue

                    prefixed_base.remove_command(str(func.sub_cmd_name))

                    if not prefixed_base.subcommands:
                        # the base cmd is now empty, delete it
                        if func.group_name:
                            _base_cmd.remove_command(str(func.group_name))  # type: ignore

                            # and now the base command is empty
                            if not _base_cmd.subcommands:  # type: ignore
                                # in case you're curious, i did try to put the below behavior
                                # in a function here, but then it turns out a weird python
                                # bug can happen if i did that
                                if cmd := self.bot.prefixed_commands.pop(str(func.name), None):
                                    for alias in cmd.aliases:
                                        self.bot.prefixed_commands.pop(alias, None)

                        elif cmd := self.bot.prefixed_commands.pop(str(func.name), None):
                            for alias in cmd.aliases:
                                self.bot.prefixed_commands.pop(alias, None)

                elif cmd := self.bot.prefixed_commands.pop(str(func.name), None):
                    for alias in cmd.aliases:
                        self.bot.prefixed_commands.pop(alias, None)

        elif isinstance(func, naff.PrefixedCommand):
            if not func.is_subcommand:
                self.bot.prefixed_commands.pop(func.name, None)
                for alias in func.aliases:
                    self.bot.prefixed_commands.pop(alias, None)
    for func in self.listeners:
        self.bot.listeners[func.event].remove(func)

    self.bot.ext.pop(self.name, None)
    logger.debug(f"{self.name} has been drop")

add_ext_auto_defer(ephemeral=False, time_until_defer=0.0)

Add a auto defer for all commands in this extension.

Parameters:

Name Type Description Default
ephemeral bool

Should the command be deferred as ephemeral

False
time_until_defer float

How long to wait before deferring automatically

0.0
Source code in naff/models/naff/extension.py
202
203
204
205
206
207
208
209
210
211
def add_ext_auto_defer(self, ephemeral: bool = False, time_until_defer: float = 0.0) -> None:
    """
    Add a auto defer for all commands in this extension.

    Args:
        ephemeral: Should the command be deferred as ephemeral
        time_until_defer: How long to wait before deferring automatically

    """
    self.auto_defer = naff.AutoDefer(enabled=True, ephemeral=ephemeral, time_until_defer=time_until_defer)

add_ext_check(coroutine)

Add a coroutine as a check for all commands in this extension to run. This coroutine must take only the parameter context.

Example Usage:
1
2
3
4
5
6
7
8
def __init__(self, bot):
    self.add_ext_check(self.example)

@staticmethod
async def example(context: Context):
    if context.author.id == 123456789:
        return True
    return False

Parameters:

Name Type Description Default
coroutine Callable[[Context], Awaitable[bool]]

The coroutine to use as a check

required
Source code in naff/models/naff/extension.py
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
def add_ext_check(self, coroutine: Callable[["Context"], Awaitable[bool]]) -> None:
    """
    Add a coroutine as a check for all commands in this extension to run. This coroutine must take **only** the parameter `context`.

    ??? Hint "Example Usage:"
        ```python
        def __init__(self, bot):
            self.add_ext_check(self.example)

        @staticmethod
        async def example(context: Context):
            if context.author.id == 123456789:
                return True
            return False
        ```
    Args:
        coroutine: The coroutine to use as a check

    """
    if not asyncio.iscoroutinefunction(coroutine):
        raise TypeError("Check must be a coroutine")

    if not self.extension_checks:
        self.extension_checks = []

    self.extension_checks.append(coroutine)

add_extension_prerun(coroutine)

Add a coroutine to be run before all commands in this Extension.

Note

Pre-runs will only be run if the commands checks pass

Example Usage:
1
2
3
4
5
def __init__(self, bot):
    self.add_extension_prerun(self.example)

async def example(self, context: Context):
    await ctx.send("I ran first")

Parameters:

Name Type Description Default
coroutine Callable[..., Coroutine]

The coroutine to run

required
Source code in naff/models/naff/extension.py
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
def add_extension_prerun(self, coroutine: Callable[..., Coroutine]) -> None:
    """
    Add a coroutine to be run **before** all commands in this Extension.

    !!! note
        Pre-runs will **only** be run if the commands checks pass

    ??? Hint "Example Usage:"
        ```python
        def __init__(self, bot):
            self.add_extension_prerun(self.example)

        async def example(self, context: Context):
            await ctx.send("I ran first")
        ```

    Args:
        coroutine: The coroutine to run

    """
    if not asyncio.iscoroutinefunction(coroutine):
        raise TypeError("Callback must be a coroutine")

    if not self.extension_prerun:
        self.extension_prerun = []
    self.extension_prerun.append(coroutine)

add_extension_postrun(coroutine)

Add a coroutine to be run after all commands in this Extension.

Example Usage:
1
2
3
4
5
def __init__(self, bot):
    self.add_extension_postrun(self.example)

async def example(self, context: Context):
    await ctx.send("I ran first")

Parameters:

Name Type Description Default
coroutine Callable[..., Coroutine]

The coroutine to run

required
Source code in naff/models/naff/extension.py
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
def add_extension_postrun(self, coroutine: Callable[..., Coroutine]) -> None:
    """
    Add a coroutine to be run **after** all commands in this Extension.

    ??? Hint "Example Usage:"
        ```python
        def __init__(self, bot):
            self.add_extension_postrun(self.example)

        async def example(self, context: Context):
            await ctx.send("I ran first")
        ```

    Args:
        coroutine: The coroutine to run

    """
    if not asyncio.iscoroutinefunction(coroutine):
        raise TypeError("Callback must be a coroutine")

    if not self.extension_postrun:
        self.extension_postrun = []
    self.extension_postrun.append(coroutine)

set_extension_error(coroutine)

Add a coroutine to handle any exceptions raised in this extension.

Example Usage:

```python def init(self, bot): self.set_extension_error(self.example)

Args: coroutine: The coroutine to run

Source code in naff/models/naff/extension.py
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
def set_extension_error(self, coroutine: Callable[..., Coroutine]) -> None:
    """
    Add a coroutine to handle any exceptions raised in this extension.

    ??? Hint "Example Usage:"
        ```python
        def __init__(self, bot):
            self.set_extension_error(self.example)

    Args:
        coroutine: The coroutine to run

    """
    if not asyncio.iscoroutinefunction(coroutine):
        raise TypeError("Callback must be a coroutine")

    if self.extension_error:
        logger.warning("Extension error callback has been overridden!")
    self.extension_error = coroutine

Buckets

Bases: IntEnum

Outlines the cooldown buckets that may be used. Should a bucket for guilds exist, and the command is invoked in a DM, a sane default will be used.

Note

To add your own, override this

Source code in naff/models/naff/cooldowns.py
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
class Buckets(IntEnum):
    """
    Outlines the cooldown buckets that may be used. Should a bucket for guilds exist, and the command is invoked in a DM, a sane default will be used.

    ??? note
         To add your own, override this

    """

    DEFAULT = 0
    """Default is the same as user"""
    USER = 1
    """Per user cooldowns"""
    GUILD = 2
    """Per guild cooldowns"""
    CHANNEL = 3
    """Per channel cooldowns"""
    MEMBER = 4
    """Per guild member cooldowns"""
    CATEGORY = 5
    """Per category cooldowns"""
    ROLE = 6
    """Per role cooldowns"""

    async def get_key(self, context: "Context") -> Any:
        if self is Buckets.USER:
            return context.author.id
        elif self is Buckets.GUILD:
            return context.guild_id if context.guild else context.author.id
        elif self is Buckets.CHANNEL:
            return context.channel.id
        elif self is Buckets.MEMBER:
            return (context.guild_id, context.author.id) if context.guild else context.author.id
        elif self is Buckets.CATEGORY:
            return await context.channel.parent_id if context.channel.parent else context.channel.id
        elif self is Buckets.ROLE:
            return context.channel.id if not context.guild else context.author.top_role.id
        else:
            return context.author.id

    def __call__(self, context: "Context") -> Any:
        return self.get_key(context)

DEFAULT = 0 class-attribute

Default is the same as user

USER = 1 class-attribute

Per user cooldowns

GUILD = 2 class-attribute

Per guild cooldowns

CHANNEL = 3 class-attribute

Per channel cooldowns

MEMBER = 4 class-attribute

Per guild member cooldowns

CATEGORY = 5 class-attribute

Per category cooldowns

ROLE = 6 class-attribute

Per role cooldowns

Cooldown

Manages cooldowns and their respective buckets for a command.

Source code in naff/models/naff/cooldowns.py
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
class Cooldown:
    """Manages cooldowns and their respective buckets for a command."""

    __slots__ = "bucket", "cooldown_repositories", "rate", "interval"

    def __init__(self, cooldown_bucket: Buckets, rate: int, interval: float) -> None:
        self.bucket: Buckets = cooldown_bucket
        self.cooldown_repositories = {}
        self.rate: int = rate
        self.interval: float = interval

    async def get_cooldown(self, context: "Context") -> "CooldownSystem":
        key = await self.bucket(context)

        if key not in self.cooldown_repositories:
            cooldown = CooldownSystem(self.rate, self.interval)
            self.cooldown_repositories[key] = cooldown
            return cooldown
        return self.cooldown_repositories.get(await self.bucket(context))

    async def acquire_token(self, context: "Context") -> bool:
        """
        Attempt to acquire a token for a command to run. Uses the context of the command to use the correct CooldownSystem.

        Args:
            context: The context of the command

        Returns:
            True if a token was acquired, False if not

        """
        cooldown = await self.get_cooldown(context)

        return cooldown.acquire_token()

    async def get_cooldown_time(self, context: "Context") -> float:
        """
        Get the remaining cooldown time.

        Args:
            context: The context of the command

        Returns:
            remaining cooldown time, will return 0 if the cooldown has not been reached

        """
        cooldown = await self.get_cooldown(context)
        return cooldown.get_cooldown_time()

    async def on_cooldown(self, context: "Context") -> bool:
        """
        Returns the cooldown state of the command.

        Args:
            context: The context of the command

        Returns:
            boolean state if the command is on cooldown or not

        """
        cooldown = await self.get_cooldown(context)
        return cooldown.on_cooldown()

    async def reset_all(self) -> None:
        """
        Resets this cooldown system to its initial state.

        !!! warning     To be clear, this will reset **all** cooldowns
        for this command to their initial states

        """
        # this doesnt need to be async, but for consistency, it is
        self.cooldown_repositories = {}

    async def reset(self, context: "Context") -> None:
        """
        Resets the cooldown for the bucket of which invoked this command.

        Args:
            context: The context of the command

        """
        cooldown = await self.get_cooldown(context)
        cooldown.reset()

acquire_token(context) async

Attempt to acquire a token for a command to run. Uses the context of the command to use the correct CooldownSystem.

Parameters:

Name Type Description Default
context Context

The context of the command

required

Returns:

Type Description
bool

True if a token was acquired, False if not

Source code in naff/models/naff/cooldowns.py
76
77
78
79
80
81
82
83
84
85
86
87
88
89
async def acquire_token(self, context: "Context") -> bool:
    """
    Attempt to acquire a token for a command to run. Uses the context of the command to use the correct CooldownSystem.

    Args:
        context: The context of the command

    Returns:
        True if a token was acquired, False if not

    """
    cooldown = await self.get_cooldown(context)

    return cooldown.acquire_token()

get_cooldown_time(context) async

Get the remaining cooldown time.

Parameters:

Name Type Description Default
context Context

The context of the command

required

Returns:

Type Description
float

remaining cooldown time, will return 0 if the cooldown has not been reached

Source code in naff/models/naff/cooldowns.py
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
async def get_cooldown_time(self, context: "Context") -> float:
    """
    Get the remaining cooldown time.

    Args:
        context: The context of the command

    Returns:
        remaining cooldown time, will return 0 if the cooldown has not been reached

    """
    cooldown = await self.get_cooldown(context)
    return cooldown.get_cooldown_time()

on_cooldown(context) async

Returns the cooldown state of the command.

Parameters:

Name Type Description Default
context Context

The context of the command

required

Returns:

Type Description
bool

boolean state if the command is on cooldown or not

Source code in naff/models/naff/cooldowns.py
105
106
107
108
109
110
111
112
113
114
115
116
117
async def on_cooldown(self, context: "Context") -> bool:
    """
    Returns the cooldown state of the command.

    Args:
        context: The context of the command

    Returns:
        boolean state if the command is on cooldown or not

    """
    cooldown = await self.get_cooldown(context)
    return cooldown.on_cooldown()

reset_all() async

Resets this cooldown system to its initial state.

!!! warning To be clear, this will reset all cooldowns for this command to their initial states

Source code in naff/models/naff/cooldowns.py
119
120
121
122
123
124
125
126
127
128
async def reset_all(self) -> None:
    """
    Resets this cooldown system to its initial state.

    !!! warning     To be clear, this will reset **all** cooldowns
    for this command to their initial states

    """
    # this doesnt need to be async, but for consistency, it is
    self.cooldown_repositories = {}

reset(context) async

Resets the cooldown for the bucket of which invoked this command.

Parameters:

Name Type Description Default
context Context

The context of the command

required
Source code in naff/models/naff/cooldowns.py
130
131
132
133
134
135
136
137
138
139
async def reset(self, context: "Context") -> None:
    """
    Resets the cooldown for the bucket of which invoked this command.

    Args:
        context: The context of the command

    """
    cooldown = await self.get_cooldown(context)
    cooldown.reset()

CooldownSystem

Represents a cooldown system for commands.

Attributes:

Name Type Description
rate int

How many commands may be ran per interval

interval float

How many seconds to wait for a cooldown

opened float

When this cooldown session began

Source code in naff/models/naff/cooldowns.py
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
class CooldownSystem:
    """
    Represents a cooldown system for commands.

    Attributes:
        rate: How many commands may be ran per interval
        interval: How many seconds to wait for a cooldown
        opened: When this cooldown session began

    """

    __slots__ = "rate", "interval", "opened", "_tokens"

    def __init__(self, rate: int, interval: float) -> None:
        self.rate: int = rate
        self.interval: float = interval
        self.opened: float = 0.0

        self._tokens: int = self.rate

        # sanity checks
        if self.rate == 0:
            raise ValueError("Cooldown rate must be greater than 0")
        if self.interval == 0:
            raise ValueError("Cooldown interval must be greater than 0")

    def reset(self) -> None:
        """Resets the tokens for this cooldown."""
        self._tokens = self.rate
        self.opened = 0.0

    def on_cooldown(self) -> bool:
        """
        Returns the cooldown state of the command.

        Returns:
            boolean state if the command is on cooldown or not
        """
        self.determine_cooldown()

        if self._tokens == 0:
            return True
        return False

    def acquire_token(self) -> bool:
        """
        Attempt to acquire a token for a command to run.

        Returns:
            True if a token was acquired, False if not

        """
        self.determine_cooldown()

        if self._tokens == 0:
            return False
        if self._tokens == self.rate:
            self.opened = time.time()
        self._tokens -= 1

        return True

    def get_cooldown_time(self) -> float:
        """
        Returns how long until the cooldown will reset.

        Returns:
            remaining cooldown time, will return 0 if the cooldown has not been reached

        """
        self.determine_cooldown()
        if self._tokens != 0:
            return 0
        return self.interval - (time.time() - self.opened)

    def determine_cooldown(self) -> None:
        """Determines the state of the cooldown system."""
        c_time = time.time()

        if c_time > self.opened + self.interval:
            # cooldown has expired, reset the cooldown
            self.reset()

reset()

Resets the tokens for this cooldown.

Source code in naff/models/naff/cooldowns.py
168
169
170
171
def reset(self) -> None:
    """Resets the tokens for this cooldown."""
    self._tokens = self.rate
    self.opened = 0.0

on_cooldown()

Returns the cooldown state of the command.

Returns:

Type Description
bool

boolean state if the command is on cooldown or not

Source code in naff/models/naff/cooldowns.py
173
174
175
176
177
178
179
180
181
182
183
184
def on_cooldown(self) -> bool:
    """
    Returns the cooldown state of the command.

    Returns:
        boolean state if the command is on cooldown or not
    """
    self.determine_cooldown()

    if self._tokens == 0:
        return True
    return False

acquire_token()

Attempt to acquire a token for a command to run.

Returns:

Type Description
bool

True if a token was acquired, False if not

Source code in naff/models/naff/cooldowns.py
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
def acquire_token(self) -> bool:
    """
    Attempt to acquire a token for a command to run.

    Returns:
        True if a token was acquired, False if not

    """
    self.determine_cooldown()

    if self._tokens == 0:
        return False
    if self._tokens == self.rate:
        self.opened = time.time()
    self._tokens -= 1

    return True

get_cooldown_time()

Returns how long until the cooldown will reset.

Returns:

Type Description
float

remaining cooldown time, will return 0 if the cooldown has not been reached

Source code in naff/models/naff/cooldowns.py
204
205
206
207
208
209
210
211
212
213
214
215
def get_cooldown_time(self) -> float:
    """
    Returns how long until the cooldown will reset.

    Returns:
        remaining cooldown time, will return 0 if the cooldown has not been reached

    """
    self.determine_cooldown()
    if self._tokens != 0:
        return 0
    return self.interval - (time.time() - self.opened)

determine_cooldown()

Determines the state of the cooldown system.

Source code in naff/models/naff/cooldowns.py
217
218
219
220
221
222
223
def determine_cooldown(self) -> None:
    """Determines the state of the cooldown system."""
    c_time = time.time()

    if c_time > self.opened + self.interval:
        # cooldown has expired, reset the cooldown
        self.reset()

MaxConcurrency

Limits how many instances of a command may be running concurrently.

Attributes:

Name Type Description
bucket Buckets

The bucket this concurrency applies to

concurrent int

The maximum number of concurrent instances permitted to

wait bool

Should we wait until a instance is available

Source code in naff/models/naff/cooldowns.py
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
class MaxConcurrency:
    """
    Limits how many instances of a command may be running concurrently.

    Attributes:
        bucket Buckets: The bucket this concurrency applies to
        concurrent int: The maximum number of concurrent instances permitted to
        wait bool: Should we wait until a instance is available

    """

    def __init__(self, concurrent: int, concurrency_bucket: Buckets, wait: bool = False) -> None:
        self.bucket: Buckets = concurrency_bucket
        self.concurrency_repository: Dict = {}
        self.concurrent: int = concurrent
        self.wait = wait

    async def get_semaphore(self, context: "Context") -> asyncio.Semaphore:
        """
        Get the semaphore associated with the given context.

        Args:
            context: The commands context

        Returns:
            A semaphore object
        """
        key = await self.bucket(context)

        if key not in self.concurrency_repository:
            semaphore = asyncio.Semaphore(self.concurrent)
            self.concurrency_repository[key] = semaphore
            return semaphore
        return self.concurrency_repository.get(key)

    async def acquire(self, context: "Context") -> bool:
        """
        Acquire an instance of the semaphore.

        Args:
            context:The context of the command
        Returns:
            If the semaphore was successfully acquired

        """
        semaphore = await self.get_semaphore(context)

        if not self.wait and semaphore.locked():
            return False
        acquired = await semaphore.acquire()
        return acquired

    async def release(self, context: "Context") -> None:
        """
        Release the semaphore.

        Args:
            context: The context of the command

        """
        semaphore = await self.get_semaphore(context)

        semaphore.release()

get_semaphore(context) async

Get the semaphore associated with the given context.

Parameters:

Name Type Description Default
context Context

The commands context

required

Returns:

Type Description
asyncio.Semaphore

A semaphore object

Source code in naff/models/naff/cooldowns.py
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
async def get_semaphore(self, context: "Context") -> asyncio.Semaphore:
    """
    Get the semaphore associated with the given context.

    Args:
        context: The commands context

    Returns:
        A semaphore object
    """
    key = await self.bucket(context)

    if key not in self.concurrency_repository:
        semaphore = asyncio.Semaphore(self.concurrent)
        self.concurrency_repository[key] = semaphore
        return semaphore
    return self.concurrency_repository.get(key)

acquire(context) async

Acquire an instance of the semaphore.

Parameters:

Name Type Description Default
context Context

The context of the command

required

Returns:

Type Description
bool

If the semaphore was successfully acquired

Source code in naff/models/naff/cooldowns.py
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
async def acquire(self, context: "Context") -> bool:
    """
    Acquire an instance of the semaphore.

    Args:
        context:The context of the command
    Returns:
        If the semaphore was successfully acquired

    """
    semaphore = await self.get_semaphore(context)

    if not self.wait and semaphore.locked():
        return False
    acquired = await semaphore.acquire()
    return acquired

release(context) async

Release the semaphore.

Parameters:

Name Type Description Default
context Context

The context of the command

required
Source code in naff/models/naff/cooldowns.py
278
279
280
281
282
283
284
285
286
287
288
async def release(self, context: "Context") -> None:
    """
    Release the semaphore.

    Args:
        context: The context of the command

    """
    semaphore = await self.get_semaphore(context)

    semaphore.release()

has_role(role)

Check if the user has the given role.

Parameters:

Name Type Description Default
role Snowflake_Type | Role

The Role or role id to check for

required
Source code in naff/models/naff/checks.py
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
def has_role(role: Snowflake_Type | Role) -> TYPE_CHECK_FUNCTION:
    """
    Check if the user has the given role.

    Args:
        role: The Role or role id to check for

    """

    async def check(ctx: Context) -> bool:
        if ctx.guild is None:
            return False
        author: Member = ctx.author  # pyright: ignore [reportGeneralTypeIssues]
        return author.has_role(role)

    return check

has_any_role(*roles)

Checks if the user has any of the given roles.

Parameters:

Name Type Description Default
*roles Snowflake_Type | Role

The Role(s) or role id(s) to check for

()
Source code in naff/models/naff/checks.py
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
def has_any_role(*roles: Snowflake_Type | Role) -> TYPE_CHECK_FUNCTION:
    """
    Checks if the user has any of the given roles.

    Args:
        *roles: The Role(s) or role id(s) to check for
    """

    async def check(ctx: Context) -> bool:
        if ctx.guild is None:
            return False

        author: Member = ctx.author  # pyright: ignore [reportGeneralTypeIssues]
        if any(author.has_role(to_snowflake(r)) for r in roles):
            return True
        return False

    return check

has_id(user_id)

Checks if the author has the desired ID.

Parameters:

Name Type Description Default
user_id int

id of the user to check for

required
Source code in naff/models/naff/checks.py
53
54
55
56
57
58
59
60
61
62
63
64
65
def has_id(user_id: int) -> TYPE_CHECK_FUNCTION:
    """
    Checks if the author has the desired ID.

    Args:
        user_id: id of the user to check for

    """

    async def check(ctx: Context) -> bool:
        return ctx.author.id == user_id

    return check

is_owner()

Checks if the author is the owner of the bot. This respects the client.owner_ids list.

Source code in naff/models/naff/checks.py
68
69
70
71
72
73
74
75
76
77
def is_owner() -> TYPE_CHECK_FUNCTION:
    """Checks if the author is the owner of the bot. This respects the `client.owner_ids` list."""

    async def check(ctx: Context) -> bool:
        _owner_ids: set = ctx.bot.owner_ids.copy()
        if ctx.bot.app.team:
            [_owner_ids.add(m.id) for m in ctx.bot.app.team.members]
        return ctx.author.id in _owner_ids

    return check

guild_only()

This command may only be ran in a guild.

Source code in naff/models/naff/checks.py
80
81
82
83
84
85
86
def guild_only() -> TYPE_CHECK_FUNCTION:
    """This command may only be ran in a guild."""

    async def check(ctx: Context) -> bool:
        return ctx.guild is not None

    return check

dm_only()

This command may only be ran in a dm.

Source code in naff/models/naff/checks.py
89
90
91
92
93
94
95
def dm_only() -> TYPE_CHECK_FUNCTION:
    """This command may only be ran in a dm."""

    async def check(ctx: Context) -> bool:
        return ctx.guild is None

    return check

A decorator to add an auto defer to a application command.

Parameters:

Name Type Description Default
ephemeral bool

Should the command be deferred as ephemeral

False
time_until_defer float

How long to wait before deferring automatically

0.0
Source code in naff/models/naff/application_commands.py
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
def auto_defer(ephemeral: bool = False, time_until_defer: float = 0.0) -> Callable[[Coroutine], Coroutine]:
    """
    A decorator to add an auto defer to a application command.

    Args:
        ephemeral: Should the command be deferred as ephemeral
        time_until_defer: How long to wait before deferring automatically

    """

    def wrapper(func: Coroutine) -> Coroutine:
        if hasattr(func, "cmd_id"):
            raise Exception("auto_defer decorators must be positioned under a slash_command decorator")
        func.auto_defer = AutoDefer(enabled=True, ephemeral=ephemeral, time_until_defer=time_until_defer)
        return func

    return wrapper

CMD_BODY

Bases: NoArgumentConverter[str]

This argument is for the body of the message.

IE:

if @bot hello how are you? is sent this argument will be hello how are you?

Source code in naff/models/naff/annotations/argument.py
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class CMD_BODY(NoArgumentConverter[str]):
    """
    This argument is for the body of the message.

    IE:

    if `@bot hello how are you?` is sent this argument will be `hello how are you?`
    """

    async def convert(self, context: Context, _) -> str:
        """Returns the body of the message."""
        if not isinstance(context, PrefixedContext):
            raise BadArgument("CMD_BODY can only be used with prefixed commands.")
        return context.content_parameters

convert(context, _) async

Returns the body of the message.

Source code in naff/models/naff/annotations/argument.py
23
24
25
26
27
async def convert(self, context: Context, _) -> str:
    """Returns the body of the message."""
    if not isinstance(context, PrefixedContext):
        raise BadArgument("CMD_BODY can only be used with prefixed commands.")
    return context.content_parameters

slash_str_option(description, required=False, autocomplete=False, choices=None, min_length=None, max_length=None)

Annotates an argument as a string type slash command option.

Parameters:

Name Type Description Default
description str

The description of your option

required
required bool

Is this option required?

False
autocomplete bool

Use autocomplete for this option

False
choices List[Union[SlashCommandChoice, dict]]

The choices allowed by this command

None
min_length Optional[int]

The minimum length of text a user can input.

None
max_length Optional[int]

The maximum length of text a user can input.

None
Source code in naff/models/naff/annotations/slash.py
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
def slash_str_option(
    description: str,
    required: bool = False,
    autocomplete: bool = False,
    choices: List[Union["SlashCommandChoice", dict]] = None,
    min_length: Optional[int] = None,
    max_length: Optional[int] = None,
) -> Type[str]:
    """
    Annotates an argument as a string type slash command option.

    Args:
        description: The description of your option
        required: Is this option required?
        autocomplete: Use autocomplete for this option
        choices: The choices allowed by this command
        min_length: The minimum length of text a user can input.
        max_length: The maximum length of text a user can input.

    """
    option = SlashCommandOption(
        name="placeholder",
        description=description,
        required=required,
        autocomplete=autocomplete,
        choices=choices or [],
        max_length=max_length,
        min_length=min_length,
        type=models.OptionTypes.STRING,
    )
    return option  # type: ignore

CMD_AUTHOR

Bases: NoArgumentConverter

This argument is the author of the context.

Source code in naff/models/naff/annotations/argument.py
30
31
32
33
34
35
class CMD_AUTHOR(NoArgumentConverter):
    """This argument is the author of the context."""

    async def convert(self, context: Context, _) -> "Member | User":
        """Returns the author of the context."""
        return context.author

convert(context, _) async

Returns the author of the context.

Source code in naff/models/naff/annotations/argument.py
33
34
35
async def convert(self, context: Context, _) -> "Member | User":
    """Returns the author of the context."""
    return context.author

CMD_CHANNEL

Bases: NoArgumentConverter

This argument is the channel the command was sent in.

Source code in naff/models/naff/annotations/argument.py
38
39
40
41
42
43
class CMD_CHANNEL(NoArgumentConverter):
    """This argument is the channel the command was sent in."""

    async def convert(self, context: Context, _) -> "TYPE_MESSAGEABLE_CHANNEL":
        """Returns the channel of the context."""
        return context.channel

convert(context, _) async

Returns the channel of the context.

Source code in naff/models/naff/annotations/argument.py
41
42
43
async def convert(self, context: Context, _) -> "TYPE_MESSAGEABLE_CHANNEL":
    """Returns the channel of the context."""
    return context.channel

CMD_ARGS

Bases: NoArgumentConverter[list[str]]

This argument is all of the arguments sent with this context.

Source code in naff/models/naff/annotations/argument.py
46
47
48
49
50
51
52
class CMD_ARGS(NoArgumentConverter[list[str]]):
    """This argument is all of the arguments sent with this context."""

    @staticmethod
    async def convert(context: Context, _) -> list[str]:
        """Returns the arguments for this context."""
        return context.args

convert(context, _) async staticmethod

Returns the arguments for this context.

Source code in naff/models/naff/annotations/argument.py
49
50
51
52
@staticmethod
async def convert(context: Context, _) -> list[str]:
    """Returns the arguments for this context."""
    return context.args

slash_float_option(description, required=False, autocomplete=False, choices=None, min_value=None, max_value=None)

Annotates an argument as a float type slash command option.

Parameters:

Name Type Description Default
description str

The description of your option

required
required bool

Is this option required?

False
autocomplete bool

Use autocomplete for this option

False
choices List[Union[SlashCommandChoice, dict]]

The choices allowed by this command

None
min_value Optional[float]

The minimum number allowed

None
max_value Optional[float]

The maximum number allowed

None
Source code in naff/models/naff/annotations/slash.py
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
def slash_float_option(
    description: str,
    required: bool = False,
    autocomplete: bool = False,
    choices: List[Union["SlashCommandChoice", dict]] = None,
    min_value: Optional[float] = None,
    max_value: Optional[float] = None,
) -> Type[float]:
    """
    Annotates an argument as a float type slash command option.

    Args:
        description: The description of your option
        required: Is this option required?
        autocomplete: Use autocomplete for this option
        choices: The choices allowed by this command
        min_value: The minimum number allowed
        max_value: The maximum number allowed

    """
    option = SlashCommandOption(
        name="placeholder",
        description=description,
        required=required,
        autocomplete=autocomplete,
        choices=choices or [],
        max_value=max_value,
        min_value=min_value,
        type=models.OptionTypes.NUMBER,
    )
    return option  # type: ignore

slash_int_option(description, required=False, autocomplete=False, choices=None, min_value=None, max_value=None)

Annotates an argument as a integer type slash command option.

Parameters:

Name Type Description Default
description str

The description of your option

required
required bool

Is this option required?

False
autocomplete bool

Use autocomplete for this option

False
choices List[Union[SlashCommandChoice, dict]]

The choices allowed by this command

None
min_value Optional[float]

The minimum number allowed

None
max_value Optional[float]

The maximum number allowed

None
Source code in naff/models/naff/annotations/slash.py
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
def slash_int_option(
    description: str,
    required: bool = False,
    autocomplete: bool = False,
    choices: List[Union["SlashCommandChoice", dict]] = None,
    min_value: Optional[float] = None,
    max_value: Optional[float] = None,
) -> Type[int]:
    """
    Annotates an argument as a integer type slash command option.

    Args:
        description: The description of your option
        required: Is this option required?
        autocomplete: Use autocomplete for this option
        choices: The choices allowed by this command
        min_value: The minimum number allowed
        max_value: The maximum number allowed

    """
    option = SlashCommandOption(
        name="placeholder",
        description=description,
        required=required,
        autocomplete=autocomplete,
        choices=choices or [],
        max_value=max_value,
        min_value=min_value,
        type=models.OptionTypes.INTEGER,
    )
    return option  # type: ignore

slash_bool_option(description, required=False)

Annotates an argument as a boolean type slash command option.

Parameters:

Name Type Description Default
description str

The description of your option

required
required bool

Is this option required?

False
Source code in naff/models/naff/annotations/slash.py
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
def slash_bool_option(
    description: str,
    required: bool = False,
) -> Type[bool]:
    """
    Annotates an argument as a boolean type slash command option.

    Args:
        description: The description of your option
        required: Is this option required?

    """
    option = SlashCommandOption(
        name="placeholder",
        description=description,
        required=required,
        type=models.OptionTypes.BOOLEAN,
    )
    return option  # type: ignore

slash_user_option(description, required=False, autocomplete=False)

Annotates an argument as a user type slash command option.

Parameters:

Name Type Description Default
description str

The description of your option

required
required bool

Is this option required?

False
autocomplete bool

Use autocomplete for this option

False
Source code in naff/models/naff/annotations/slash.py
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
def slash_user_option(
    description: str,
    required: bool = False,
    autocomplete: bool = False,
) -> Type[Union["User", "Member"]]:
    """
    Annotates an argument as a user type slash command option.

    Args:
        description: The description of your option
        required: Is this option required?
        autocomplete: Use autocomplete for this option

    """
    option = SlashCommandOption(
        name="placeholder",
        description=description,
        required=required,
        autocomplete=autocomplete,
        type=models.OptionTypes.USER,
    )
    return option  # type: ignore

slash_channel_option(description, required=False, autocomplete=False, choices=None, channel_types=None)

Annotates an argument as a channel type slash command option.

Parameters:

Name Type Description Default
description str

The description of your option

required
required bool

Is this option required?

False
autocomplete bool

Use autocomplete for this option

False
choices List[Union[SlashCommandChoice, dict]]

The choices allowed by this command

None
channel_types Optional[list[Union[ChannelTypes, int]]]

The types of channel allowed by this option

None
Source code in naff/models/naff/annotations/slash.py
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
def slash_channel_option(
    description: str,
    required: bool = False,
    autocomplete: bool = False,
    choices: List[Union["SlashCommandChoice", dict]] = None,
    channel_types: Optional[list[Union["ChannelTypes", int]]] = None,
) -> Type["BaseChannel"]:
    """
    Annotates an argument as a channel type slash command option.

    Args:
        description: The description of your option
        required: Is this option required?
        autocomplete: Use autocomplete for this option
        choices: The choices allowed by this command
        channel_types: The types of channel allowed by this option

    """
    option = SlashCommandOption(
        name="placeholder",
        description=description,
        required=required,
        autocomplete=autocomplete,
        choices=choices or [],
        channel_types=channel_types,
        type=models.OptionTypes.CHANNEL,
    )
    return option  # type: ignore

slash_role_option(description, required=False, autocomplete=False, choices=None)

Annotates an argument as a role type slash command option.

Parameters:

Name Type Description Default
description str

The description of your option

required
required bool

Is this option required?

False
autocomplete bool

Use autocomplete for this option

False
choices List[Union[SlashCommandChoice, dict]]

The choices allowed by this command

None
Source code in naff/models/naff/annotations/slash.py
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
def slash_role_option(
    description: str,
    required: bool = False,
    autocomplete: bool = False,
    choices: List[Union["SlashCommandChoice", dict]] = None,
) -> Type["Role"]:
    """
    Annotates an argument as a role type slash command option.

    Args:
        description: The description of your option
        required: Is this option required?
        autocomplete: Use autocomplete for this option
        choices: The choices allowed by this command

    """
    option = SlashCommandOption(
        name="placeholder",
        description=description,
        required=required,
        autocomplete=autocomplete,
        choices=choices or [],
        type=models.OptionTypes.ROLE,
    )
    return option  # type: ignore

slash_mentionable_option(description, required=False, autocomplete=False, choices=None)

Annotates an argument as a mentionable type slash command option.

Parameters:

Name Type Description Default
description str

The description of your option

required
required bool

Is this option required?

False
autocomplete bool

Use autocomplete for this option

False
choices List[Union[SlashCommandChoice, dict]]

The choices allowed by this command

None
Source code in naff/models/naff/annotations/slash.py
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
def slash_mentionable_option(
    description: str,
    required: bool = False,
    autocomplete: bool = False,
    choices: List[Union["SlashCommandChoice", dict]] = None,
) -> Type[Union["Role", "BaseChannel", "User", "Member"]]:
    """
    Annotates an argument as a mentionable type slash command option.

    Args:
        description: The description of your option
        required: Is this option required?
        autocomplete: Use autocomplete for this option
        choices: The choices allowed by this command

    """
    option = SlashCommandOption(
        name="placeholder",
        description=description,
        required=required,
        autocomplete=autocomplete,
        choices=choices or [],
        type=models.OptionTypes.MENTIONABLE,
    )
    return option  # type: ignore

slash_attachment_option(description, required=False)

Annotates an argument as an attachment type slash command option.

Parameters:

Name Type Description Default
description str

The description of your option

required
required bool

Is this option required?

False
Source code in naff/models/naff/annotations/slash.py
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
def slash_attachment_option(
    description: str,
    required: bool = False,
) -> Type["Attachment"]:
    """
    Annotates an argument as an attachment type slash command option.

    Args:
        description: The description of your option
        required: Is this option required?

    """
    option = SlashCommandOption(
        name="placeholder", description=description, required=required, type=models.OptionTypes.ATTACHMENT
    )

    return option  # type: ignore

NoArgumentConverter

Bases: Converter[T_co]

An indicator class for special type of converters that only uses the Context.

This is mainly needed for prefixed commands, as arguments will be "eaten up" by converters otherwise.

Source code in naff/models/naff/converters.py
74
75
76
77
78
79
class NoArgumentConverter(Converter[T_co]):
    """
    An indicator class for special type of converters that only uses the Context.

    This is mainly needed for prefixed commands, as arguments will be "eaten up" by converters otherwise.
    """

IDConverter

Bases: Converter[T_co]

The base converter for objects that have snowflake IDs.

Source code in naff/models/naff/converters.py
104
105
106
107
108
109
class IDConverter(Converter[T_co]):
    """The base converter for objects that have snowflake IDs."""

    @staticmethod
    def _get_id_match(argument: str) -> Optional[re.Match[str]]:
        return _ID_REGEX.match(argument)

SnowflakeConverter

Bases: IDConverter[SnowflakeObject]

Converts a string argument to a SnowflakeObject.

Source code in naff/models/naff/converters.py
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
class SnowflakeConverter(IDConverter[SnowflakeObject]):
    """Converts a string argument to a SnowflakeObject."""

    async def convert(self, ctx: Context, argument: str) -> SnowflakeObject:
        """
        Converts a given string to a SnowflakeObject.

        The lookup strategy is as follows:

        1. By raw snowflake ID.

        2. By role or channel mention.

        Args:
            ctx: The context to use for the conversion.
            argument: The argument to be converted.

        Returns:
            SnowflakeObject: The converted object.
        """
        match = self._get_id_match(argument) or re.match(r"<(?:@(?:!|&)?|#)([0-9]{15,})>$", argument)

        if match is None:
            raise BadArgument(argument)

        return SnowflakeObject(int(match.group(1)))  # type: ignore

convert(ctx, argument) async

Converts a given string to a SnowflakeObject.

The lookup strategy is as follows:

  1. By raw snowflake ID.

  2. By role or channel mention.

Parameters:

Name Type Description Default
ctx Context

The context to use for the conversion.

required
argument str

The argument to be converted.

required

Returns:

Name Type Description
SnowflakeObject SnowflakeObject

The converted object.

Source code in naff/models/naff/converters.py
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
async def convert(self, ctx: Context, argument: str) -> SnowflakeObject:
    """
    Converts a given string to a SnowflakeObject.

    The lookup strategy is as follows:

    1. By raw snowflake ID.

    2. By role or channel mention.

    Args:
        ctx: The context to use for the conversion.
        argument: The argument to be converted.

    Returns:
        SnowflakeObject: The converted object.
    """
    match = self._get_id_match(argument) or re.match(r"<(?:@(?:!|&)?|#)([0-9]{15,})>$", argument)

    if match is None:
        raise BadArgument(argument)

    return SnowflakeObject(int(match.group(1)))  # type: ignore

ChannelConverter

Bases: IDConverter[T_co]

The base converter for channel objects.

Source code in naff/models/naff/converters.py
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
class ChannelConverter(IDConverter[T_co]):
    """The base converter for channel objects."""

    def _check(self, result: BaseChannel) -> bool:
        return True

    async def convert(self, ctx: Context, argument: str) -> T_co:
        """
        Converts a given string to a Channel object.

        The lookup strategy is as follows:

        1. By raw snowflake ID.

        2. By channel mention.

        3. By name - the bot will search in a guild if the context has it, otherwise it will search globally.

        Args:
            ctx: The context to use for the conversion.
            argument: The argument to be converted.

        Returns:
            BaseChannel: The converted object.
            The channel type will be of the type the converter represents.
        """
        match = self._get_id_match(argument) or re.match(r"<#([0-9]{15,})>$", argument)
        result = None

        if match:
            result = await ctx.bot.fetch_channel(int(match.group(1)))
        elif ctx.guild:
            result = next((c for c in ctx.guild.channels if c.name == argument), None)
        else:
            result = next((c for c in ctx.bot.cache.channel_cache.values() if c.name == argument), None)

        if not result:
            raise BadArgument(f'Channel "{argument}" not found.')

        if self._check(result):
            return result  # type: ignore

        raise BadArgument(f'Channel "{argument}" not found.')

convert(ctx, argument) async

Converts a given string to a Channel object.

The lookup strategy is as follows:

  1. By raw snowflake ID.

  2. By channel mention.

  3. By name - the bot will search in a guild if the context has it, otherwise it will search globally.

Parameters:

Name Type Description Default
ctx Context

The context to use for the conversion.

required
argument str

The argument to be converted.

required

Returns:

Name Type Description
BaseChannel T_co

The converted object.

T_co

The channel type will be of the type the converter represents.

Source code in naff/models/naff/converters.py
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
async def convert(self, ctx: Context, argument: str) -> T_co:
    """
    Converts a given string to a Channel object.

    The lookup strategy is as follows:

    1. By raw snowflake ID.

    2. By channel mention.

    3. By name - the bot will search in a guild if the context has it, otherwise it will search globally.

    Args:
        ctx: The context to use for the conversion.
        argument: The argument to be converted.

    Returns:
        BaseChannel: The converted object.
        The channel type will be of the type the converter represents.
    """
    match = self._get_id_match(argument) or re.match(r"<#([0-9]{15,})>$", argument)
    result = None

    if match:
        result = await ctx.bot.fetch_channel(int(match.group(1)))
    elif ctx.guild:
        result = next((c for c in ctx.guild.channels if c.name == argument), None)
    else:
        result = next((c for c in ctx.bot.cache.channel_cache.values() if c.name == argument), None)

    if not result:
        raise BadArgument(f'Channel "{argument}" not found.')

    if self._check(result):
        return result  # type: ignore

    raise BadArgument(f'Channel "{argument}" not found.')

UserConverter

Bases: IDConverter[User]

Converts a string argument to a User object.

Source code in naff/models/naff/converters.py
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
class UserConverter(IDConverter[User]):
    """Converts a string argument to a User object."""

    async def convert(self, ctx: Context, argument: str) -> User:
        """
        Converts a given string to a User object.

        The lookup strategy is as follows:

        1. By raw snowflake ID.

        2. By mention.

        3. By username + tag (ex User#1234).

        4. By username.

        Args:
            ctx: The context to use for the conversion.
            argument: The argument to be converted.

        Returns:
            User: The converted object.
        """
        match = self._get_id_match(argument) or re.match(r"<@!?([0-9]{15,})>$", argument)
        result = None

        if match:
            result = await ctx.bot.fetch_user(int(match.group(1)))
        else:
            if len(argument) > 5 and argument[-5] == "#":
                result = next((u for u in ctx.bot.cache.user_cache.values() if u.tag == argument), None)

            if not result:
                result = next((u for u in ctx.bot.cache.user_cache.values() if u.username == argument), None)

        if not result:
            raise BadArgument(f'User "{argument}" not found.')

        return result

convert(ctx, argument) async

Converts a given string to a User object.

The lookup strategy is as follows:

  1. By raw snowflake ID.

  2. By mention.

  3. By username + tag (ex User#1234).

  4. By username.

Parameters:

Name Type Description Default
ctx Context

The context to use for the conversion.

required
argument str

The argument to be converted.

required

Returns:

Name Type Description
User User

The converted object.

Source code in naff/models/naff/converters.py
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
async def convert(self, ctx: Context, argument: str) -> User:
    """
    Converts a given string to a User object.

    The lookup strategy is as follows:

    1. By raw snowflake ID.

    2. By mention.

    3. By username + tag (ex User#1234).

    4. By username.

    Args:
        ctx: The context to use for the conversion.
        argument: The argument to be converted.

    Returns:
        User: The converted object.
    """
    match = self._get_id_match(argument) or re.match(r"<@!?([0-9]{15,})>$", argument)
    result = None

    if match:
        result = await ctx.bot.fetch_user(int(match.group(1)))
    else:
        if len(argument) > 5 and argument[-5] == "#":
            result = next((u for u in ctx.bot.cache.user_cache.values() if u.tag == argument), None)

        if not result:
            result = next((u for u in ctx.bot.cache.user_cache.values() if u.username == argument), None)

    if not result:
        raise BadArgument(f'User "{argument}" not found.')

    return result

MemberConverter

Bases: IDConverter[Member]

Converts a string argument to a Member object.

Source code in naff/models/naff/converters.py
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
class MemberConverter(IDConverter[Member]):
    """Converts a string argument to a Member object."""

    def _get_member_from_list(self, members: list[Member], argument: str) -> Optional[Member]:
        # sourcery skip: assign-if-exp
        result = None
        if len(argument) > 5 and argument[-5] == "#":
            result = next((m for m in members if m.user.tag == argument), None)

        if not result:
            result = next((m for m in members if m.display_name == argument or m.user.username == argument), None)

        return result

    async def convert(self, ctx: Context, argument: str) -> Member:
        """
        Converts a given string to a Member object. This will only work in guilds.

        The lookup strategy is as follows:

        1. By raw snowflake ID.

        2. By mention.

        3. By username + tag (ex User#1234).

        4. By nickname.

        5. By username.

        Args:
            ctx: The context to use for the conversion.
            argument: The argument to be converted.

        Returns:
            Member: The converted object.
        """
        if not ctx.guild:
            raise BadArgument("This command cannot be used in private messages.")

        match = self._get_id_match(argument) or re.match(r"<@!?([0-9]{15,})>$", argument)
        result = None

        if match:
            result = await ctx.guild.fetch_member(int(match.group(1)))
        elif ctx.guild.chunked:
            result = self._get_member_from_list(ctx.guild.members, argument)
        else:
            query = argument
            if len(argument) > 5 and argument[-5] == "#":
                query, _, _ = argument.rpartition("#")

            members = await ctx.guild.search_members(query, limit=100)
            result = self._get_member_from_list(members, argument)

        if not result:
            raise BadArgument(f'Member "{argument}" not found.')

        return result

convert(ctx, argument) async

Converts a given string to a Member object. This will only work in guilds.

The lookup strategy is as follows:

  1. By raw snowflake ID.

  2. By mention.

  3. By username + tag (ex User#1234).

  4. By nickname.

  5. By username.

Parameters:

Name Type Description Default
ctx Context

The context to use for the conversion.

required
argument str

The argument to be converted.

required

Returns:

Name Type Description
Member Member

The converted object.

Source code in naff/models/naff/converters.py
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
async def convert(self, ctx: Context, argument: str) -> Member:
    """
    Converts a given string to a Member object. This will only work in guilds.

    The lookup strategy is as follows:

    1. By raw snowflake ID.

    2. By mention.

    3. By username + tag (ex User#1234).

    4. By nickname.

    5. By username.

    Args:
        ctx: The context to use for the conversion.
        argument: The argument to be converted.

    Returns:
        Member: The converted object.
    """
    if not ctx.guild:
        raise BadArgument("This command cannot be used in private messages.")

    match = self._get_id_match(argument) or re.match(r"<@!?([0-9]{15,})>$", argument)
    result = None

    if match:
        result = await ctx.guild.fetch_member(int(match.group(1)))
    elif ctx.guild.chunked:
        result = self._get_member_from_list(ctx.guild.members, argument)
    else:
        query = argument
        if len(argument) > 5 and argument[-5] == "#":
            query, _, _ = argument.rpartition("#")

        members = await ctx.guild.search_members(query, limit=100)
        result = self._get_member_from_list(members, argument)

    if not result:
        raise BadArgument(f'Member "{argument}" not found.')

    return result

MessageConverter

Bases: Converter[Message]

Converts a string argument to a Message object.

Source code in naff/models/naff/converters.py
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
class MessageConverter(Converter[Message]):
    """Converts a string argument to a Message object."""

    # either just the id or <chan_id>-<mes_id>, a format you can get by shift clicking "copy id"
    _ID_REGEX = re.compile(r"(?:(?P<channel_id>[0-9]{15,})-)?(?P<message_id>[0-9]{15,})")
    # of course, having a way to get it from a link is nice
    _MESSAGE_LINK_REGEX = re.compile(
        r"https?://[\S]*?discord(?:app)?\.com/channels/(?P<guild_id>[0-9]{15,}|@me)/(?P<channel_id>[0-9]{15,})/(?P<message_id>[0-9]{15,})\/?$"
    )

    async def convert(self, ctx: Context, argument: str) -> Message:
        """
        Converts a given string to a Message object.

        The lookup strategy is as follows:

        1. By raw snowflake ID. The message must be in the same channel as the context.

        2. By message + channel ID in the format of "{Channel ID}-{Message ID}". This can be obtained by shift clicking "Copy ID" when Developer Mode is enabled.

        3. By message link.

        Args:
            ctx: The context to use for the conversion.
            argument: The argument to be converted.

        Returns:
            Message: The converted object.
        """
        match = self._ID_REGEX.match(argument) or self._MESSAGE_LINK_REGEX.match(argument)
        if not match:
            raise BadArgument(f'Message "{argument}" not found.')

        data = match.groupdict()

        message_id = data["message_id"]
        channel_id = int(data["channel_id"]) if data.get("channel_id") else ctx.channel.id

        # this guild checking is technically unnecessary, but we do it just in case
        # it means a user cant just provide an invalid guild id and still get a message
        guild_id = data["guild_id"] if data.get("guild_id") else ctx.guild_id
        guild_id = int(guild_id) if guild_id != "@me" else None

        try:
            # this takes less possible requests than getting the guild and/or channel
            mes = await ctx.bot.cache.fetch_message(channel_id, message_id)
            if mes._guild_id != guild_id:
                raise BadArgument(f'Message "{argument}" not found.')
            return mes
        except Forbidden as e:
            raise BadArgument(f"Cannot read messages for <#{channel_id}>.") from e
        except HTTPException as e:
            raise BadArgument(f'Message "{argument}" not found.') from e

convert(ctx, argument) async

Converts a given string to a Message object.

The lookup strategy is as follows:

  1. By raw snowflake ID. The message must be in the same channel as the context.

  2. By message + channel ID in the format of "{Channel ID}-{Message ID}". This can be obtained by shift clicking "Copy ID" when Developer Mode is enabled.

  3. By message link.

Parameters:

Name Type Description Default
ctx Context

The context to use for the conversion.

required
argument str

The argument to be converted.

required

Returns:

Name Type Description
Message Message

The converted object.

Source code in naff/models/naff/converters.py
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
async def convert(self, ctx: Context, argument: str) -> Message:
    """
    Converts a given string to a Message object.

    The lookup strategy is as follows:

    1. By raw snowflake ID. The message must be in the same channel as the context.

    2. By message + channel ID in the format of "{Channel ID}-{Message ID}". This can be obtained by shift clicking "Copy ID" when Developer Mode is enabled.

    3. By message link.

    Args:
        ctx: The context to use for the conversion.
        argument: The argument to be converted.

    Returns:
        Message: The converted object.
    """
    match = self._ID_REGEX.match(argument) or self._MESSAGE_LINK_REGEX.match(argument)
    if not match:
        raise BadArgument(f'Message "{argument}" not found.')

    data = match.groupdict()

    message_id = data["message_id"]
    channel_id = int(data["channel_id"]) if data.get("channel_id") else ctx.channel.id

    # this guild checking is technically unnecessary, but we do it just in case
    # it means a user cant just provide an invalid guild id and still get a message
    guild_id = data["guild_id"] if data.get("guild_id") else ctx.guild_id
    guild_id = int(guild_id) if guild_id != "@me" else None

    try:
        # this takes less possible requests than getting the guild and/or channel
        mes = await ctx.bot.cache.fetch_message(channel_id, message_id)
        if mes._guild_id != guild_id:
            raise BadArgument(f'Message "{argument}" not found.')
        return mes
    except Forbidden as e:
        raise BadArgument(f"Cannot read messages for <#{channel_id}>.") from e
    except HTTPException as e:
        raise BadArgument(f'Message "{argument}" not found.') from e

GuildConverter

Bases: IDConverter[Guild]

Converts a string argument to a Guild object.

Source code in naff/models/naff/converters.py
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
class GuildConverter(IDConverter[Guild]):
    """Converts a string argument to a Guild object."""

    async def convert(self, ctx: Context, argument: str) -> Guild:
        """
        Converts a given string to a Guild object.

        The lookup strategy is as follows:

        1. By raw snowflake ID.

        2. By name.

        Args:
            ctx: The context to use for the conversion.
            argument: The argument to be converted.

        Returns:
            Guild: The converted object.
        """
        match = self._get_id_match(argument)
        result = None

        if match:
            result = await ctx.bot.fetch_guild(int(match.group(1)))
        else:
            result = next((g for g in ctx.bot.guilds if g.name == argument), None)

        if not result:
            raise BadArgument(f'Guild "{argument}" not found.')

        return result

convert(ctx, argument) async

Converts a given string to a Guild object.

The lookup strategy is as follows:

  1. By raw snowflake ID.

  2. By name.

Parameters:

Name Type Description Default
ctx Context

The context to use for the conversion.

required
argument str

The argument to be converted.

required

Returns:

Name Type Description
Guild Guild

The converted object.

Source code in naff/models/naff/converters.py
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
async def convert(self, ctx: Context, argument: str) -> Guild:
    """
    Converts a given string to a Guild object.

    The lookup strategy is as follows:

    1. By raw snowflake ID.

    2. By name.

    Args:
        ctx: The context to use for the conversion.
        argument: The argument to be converted.

    Returns:
        Guild: The converted object.
    """
    match = self._get_id_match(argument)
    result = None

    if match:
        result = await ctx.bot.fetch_guild(int(match.group(1)))
    else:
        result = next((g for g in ctx.bot.guilds if g.name == argument), None)

    if not result:
        raise BadArgument(f'Guild "{argument}" not found.')

    return result

RoleConverter

Bases: IDConverter[Role]

Converts a string argument to a Role object.

Source code in naff/models/naff/converters.py
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
class RoleConverter(IDConverter[Role]):
    """Converts a string argument to a Role object."""

    async def convert(self, ctx: Context, argument: str) -> Role:
        """
        Converts a given string to a Role object.

        The lookup strategy is as follows:

        1. By raw snowflake ID.

        2. By mention.

        3. By name.

        Args:
            ctx: The context to use for the conversion.
            argument: The argument to be converted.

        Returns:
            Role: The converted object.
        """
        if not ctx.guild:
            raise BadArgument("This command cannot be used in private messages.")

        match = self._get_id_match(argument) or re.match(r"<@&([0-9]{15,})>$", argument)
        result = None

        if match:
            result = await ctx.guild.fetch_role(int(match.group(1)))
        else:
            result = next((r for r in ctx.guild.roles if r.name == argument), None)

        if not result:
            raise BadArgument(f'Role "{argument}" not found.')

        return result

convert(ctx, argument) async

Converts a given string to a Role object.

The lookup strategy is as follows:

  1. By raw snowflake ID.

  2. By mention.

  3. By name.

Parameters:

Name Type Description Default
ctx Context

The context to use for the conversion.

required
argument str

The argument to be converted.

required

Returns:

Name Type Description
Role Role

The converted object.

Source code in naff/models/naff/converters.py
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
async def convert(self, ctx: Context, argument: str) -> Role:
    """
    Converts a given string to a Role object.

    The lookup strategy is as follows:

    1. By raw snowflake ID.

    2. By mention.

    3. By name.

    Args:
        ctx: The context to use for the conversion.
        argument: The argument to be converted.

    Returns:
        Role: The converted object.
    """
    if not ctx.guild:
        raise BadArgument("This command cannot be used in private messages.")

    match = self._get_id_match(argument) or re.match(r"<@&([0-9]{15,})>$", argument)
    result = None

    if match:
        result = await ctx.guild.fetch_role(int(match.group(1)))
    else:
        result = next((r for r in ctx.guild.roles if r.name == argument), None)

    if not result:
        raise BadArgument(f'Role "{argument}" not found.')

    return result

PartialEmojiConverter

Bases: IDConverter[PartialEmoji]

Converts a string argument to a PartialEmoji object.

Source code in naff/models/naff/converters.py
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
class PartialEmojiConverter(IDConverter[PartialEmoji]):
    """Converts a string argument to a PartialEmoji object."""

    async def convert(self, ctx: Context, argument: str) -> PartialEmoji:
        """
        Converts a given string to a PartialEmoji object.

        This converter only accepts emoji strings.

        Args:
            ctx: The context to use for the conversion.
            argument: The argument to be converted.

        Returns:
            PartialEmoji: The converted object.
        """
        if match := re.match(r"<a?:[a-zA-Z0-9\_]{1,32}:([0-9]{15,})>$", argument):
            emoji_animated = bool(match.group(1))
            emoji_name = match.group(2)
            emoji_id = int(match.group(3))

            return PartialEmoji(id=emoji_id, name=emoji_name, animated=emoji_animated)  # type: ignore

        raise BadArgument(f'Couldn\'t convert "{argument}" to {PartialEmoji.__name__}.')

convert(ctx, argument) async

Converts a given string to a PartialEmoji object.

This converter only accepts emoji strings.

Parameters:

Name Type Description Default
ctx Context

The context to use for the conversion.

required
argument str

The argument to be converted.

required

Returns:

Name Type Description
PartialEmoji PartialEmoji

The converted object.

Source code in naff/models/naff/converters.py
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
async def convert(self, ctx: Context, argument: str) -> PartialEmoji:
    """
    Converts a given string to a PartialEmoji object.

    This converter only accepts emoji strings.

    Args:
        ctx: The context to use for the conversion.
        argument: The argument to be converted.

    Returns:
        PartialEmoji: The converted object.
    """
    if match := re.match(r"<a?:[a-zA-Z0-9\_]{1,32}:([0-9]{15,})>$", argument):
        emoji_animated = bool(match.group(1))
        emoji_name = match.group(2)
        emoji_id = int(match.group(3))

        return PartialEmoji(id=emoji_id, name=emoji_name, animated=emoji_animated)  # type: ignore

    raise BadArgument(f'Couldn\'t convert "{argument}" to {PartialEmoji.__name__}.')

CustomEmojiConverter

Bases: IDConverter[CustomEmoji]

Converts a string argument to a CustomEmoji object.

Source code in naff/models/naff/converters.py
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
class CustomEmojiConverter(IDConverter[CustomEmoji]):
    """Converts a string argument to a CustomEmoji object."""

    async def convert(self, ctx: Context, argument: str) -> CustomEmoji:
        """
        Converts a given string to a CustomEmoji object.

        The lookup strategy is as follows:

        1. By raw snowflake ID.

        2. By the emoji string format.

        3. By name.

        Args:
            ctx: The context to use for the conversion.
            argument: The argument to be converted.

        Returns:
            CustomEmoji: The converted object.
        """
        if not ctx.guild:
            raise BadArgument("This command cannot be used in private messages.")

        match = self._get_id_match(argument) or re.match(r"<a?:[a-zA-Z0-9\_]{1,32}:([0-9]{15,})>$", argument)
        result = None

        if match:
            result = await ctx.guild.fetch_custom_emoji(int(match.group(1)))
        else:
            if ctx.bot.cache.enable_emoji_cache:
                emojis = ctx.bot.cache.emoji_cache.values()  # type: ignore
                result = next((e for e in emojis if e.name == argument))

            if not result:
                emojis = await ctx.guild.fetch_all_custom_emojis()
                result = next((e for e in emojis if e.name == argument))

        if not result:
            raise BadArgument(f'Emoji "{argument}" not found.')

        return result

convert(ctx, argument) async

Converts a given string to a CustomEmoji object.

The lookup strategy is as follows:

  1. By raw snowflake ID.

  2. By the emoji string format.

  3. By name.

Parameters:

Name Type Description Default
ctx Context

The context to use for the conversion.

required
argument str

The argument to be converted.

required

Returns:

Name Type Description
CustomEmoji CustomEmoji

The converted object.

Source code in naff/models/naff/converters.py
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
async def convert(self, ctx: Context, argument: str) -> CustomEmoji:
    """
    Converts a given string to a CustomEmoji object.

    The lookup strategy is as follows:

    1. By raw snowflake ID.

    2. By the emoji string format.

    3. By name.

    Args:
        ctx: The context to use for the conversion.
        argument: The argument to be converted.

    Returns:
        CustomEmoji: The converted object.
    """
    if not ctx.guild:
        raise BadArgument("This command cannot be used in private messages.")

    match = self._get_id_match(argument) or re.match(r"<a?:[a-zA-Z0-9\_]{1,32}:([0-9]{15,})>$", argument)
    result = None

    if match:
        result = await ctx.guild.fetch_custom_emoji(int(match.group(1)))
    else:
        if ctx.bot.cache.enable_emoji_cache:
            emojis = ctx.bot.cache.emoji_cache.values()  # type: ignore
            result = next((e for e in emojis if e.name == argument))

        if not result:
            emojis = await ctx.guild.fetch_all_custom_emojis()
            result = next((e for e in emojis if e.name == argument))

    if not result:
        raise BadArgument(f'Emoji "{argument}" not found.')

    return result

Greedy

Bases: List[T]

A special marker class to mark an argument in a prefixed command to repeatedly convert until it fails to convert an argument.

Source code in naff/models/naff/converters.py
569
570
class Greedy(List[T]):
    """A special marker class to mark an argument in a prefixed command to repeatedly convert until it fails to convert an argument."""

NAFF_MODEL_TO_CONVERTER: dict[type, type[Converter]] = {SnowflakeObject: SnowflakeConverter, BaseChannel: BaseChannelConverter, DMChannel: DMChannelConverter, DM: DMConverter, DMGroup: DMGroupConverter, GuildChannel: GuildChannelConverter, GuildNews: GuildNewsConverter, GuildCategory: GuildCategoryConverter, GuildText: GuildTextConverter, ThreadChannel: ThreadChannelConverter, GuildNewsThread: GuildNewsThreadConverter, GuildPublicThread: GuildPublicThreadConverter, GuildPrivateThread: GuildPrivateThreadConverter, VoiceChannel: VoiceChannelConverter, GuildVoice: GuildVoiceConverter, GuildStageVoice: GuildStageVoiceConverter, TYPE_ALL_CHANNEL: BaseChannelConverter, TYPE_DM_CHANNEL: DMChannelConverter, TYPE_GUILD_CHANNEL: GuildChannelConverter, TYPE_THREAD_CHANNEL: ThreadChannelConverter, TYPE_VOICE_CHANNEL: VoiceChannelConverter, TYPE_MESSAGEABLE_CHANNEL: MessageableChannelConverter, User: UserConverter, Member: MemberConverter, Message: MessageConverter, Guild: GuildConverter, Role: RoleConverter, PartialEmoji: PartialEmojiConverter, CustomEmoji: CustomEmojiConverter} module-attribute

A dictionary mapping of naff objects to their corresponding converters.

Listener

Bases: CallbackObject

Source code in naff/models/naff/listener.py
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
class Listener(CallbackObject):

    event: str
    """Name of the event to listen to."""
    callback: Coroutine
    """Coroutine to call when the event is triggered."""

    def __init__(self, func: Callable[..., Coroutine], event: str) -> None:
        super().__init__()

        self.event = event
        self.callback = func

    @classmethod
    def create(cls, event_name: Absent[str | BaseEvent] = MISSING) -> Callable[[Coroutine], "Listener"]:
        """
        Decorator for creating an event listener.

        Args:
            event_name: The name of the event to listen to. If left blank, event name will be inferred from the function name or parameter.

        Returns:
            A listener object.

        """

        def wrapper(coro: Coroutine) -> "Listener":
            if not asyncio.iscoroutinefunction(coro):
                raise TypeError("Listener must be a coroutine")

            name = event_name

            if name is MISSING:
                for typehint in coro.__annotations__.values():
                    if (
                        inspect.isclass(typehint)
                        and issubclass(typehint, BaseEvent)
                        and typehint.__name__ != "RawGatewayEvent"
                    ):
                        name = typehint.__name__
                        break

                if not name:
                    name = coro.__name__

            return cls(coro, get_event_name(name))

        return wrapper

create(event_name=MISSING) classmethod

Decorator for creating an event listener.

Parameters:

Name Type Description Default
event_name Absent[str | BaseEvent]

The name of the event to listen to. If left blank, event name will be inferred from the function name or parameter.

MISSING

Returns:

Type Description
Callable[[Coroutine], Listener]

A listener object.

Source code in naff/models/naff/listener.py
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
@classmethod
def create(cls, event_name: Absent[str | BaseEvent] = MISSING) -> Callable[[Coroutine], "Listener"]:
    """
    Decorator for creating an event listener.

    Args:
        event_name: The name of the event to listen to. If left blank, event name will be inferred from the function name or parameter.

    Returns:
        A listener object.

    """

    def wrapper(coro: Coroutine) -> "Listener":
        if not asyncio.iscoroutinefunction(coro):
            raise TypeError("Listener must be a coroutine")

        name = event_name

        if name is MISSING:
            for typehint in coro.__annotations__.values():
                if (
                    inspect.isclass(typehint)
                    and issubclass(typehint, BaseEvent)
                    and typehint.__name__ != "RawGatewayEvent"
                ):
                    name = typehint.__name__
                    break

            if not name:
                name = coro.__name__

        return cls(coro, get_event_name(name))

    return wrapper

listen(event_name=MISSING)

Decorator to make a function an event listener.

Parameters:

Name Type Description Default
event_name Absent[str | BaseEvent]

The name of the event to listen to. If left blank, event name will be inferred from the function name or parameter.

MISSING

Returns:

Type Description
Callable[[Callable[..., Coroutine]], Listener]

A listener object.

Source code in naff/models/naff/listener.py
63
64
65
66
67
68
69
70
71
72
73
74
def listen(event_name: Absent[str | BaseEvent] = MISSING) -> Callable[[Callable[..., Coroutine]], Listener]:
    """
    Decorator to make a function an event listener.

    Args:
        event_name: The name of the event to listen to. If left blank, event name will be inferred from the function name or parameter.

    Returns:
        A listener object.

    """
    return Listener.create(event_name)

LocalisedField

An object that enables support for localising fields.

Supported locales: https://discord.com/developers/docs/reference#locales

Source code in naff/models/naff/localisation.py
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
@define(slots=False)
class LocalisedField:
    """
    An object that enables support for localising fields.

    Supported locales: https://discord.com/developers/docs/reference#locales
    """

    default_locale: str = field(default=const.default_locale)

    bulgarian: str | None = field(default=None, metadata={"locale-code": "bg"})
    chinese_china: str | None = field(default=None, metadata={"locale-code": "zh-CN"})
    chinese_taiwan: str | None = field(default=None, metadata={"locale-code": "zh-TW"})
    croatian: str | None = field(default=None, metadata={"locale-code": "hr"})
    czech: str | None = field(default=None, metadata={"locale-code": "cs"})
    danish: str | None = field(default=None, metadata={"locale-code": "da"})
    dutch: str | None = field(default=None, metadata={"locale-code": "nl"})
    english_uk: str | None = field(default=None, metadata={"locale-code": "en-GB"})
    english_us: str | None = field(default=None, metadata={"locale-code": "en-US"})
    finnish: str | None = field(default=None, metadata={"locale-code": "fi"})
    french: str | None = field(default=None, metadata={"locale-code": "fr"})
    german: str | None = field(default=None, metadata={"locale-code": "de"})
    greek: str | None = field(default=None, metadata={"locale-code": "el"})
    hindi: str | None = field(default=None, metadata={"locale-code": "hi"})
    hungarian: str | None = field(default=None, metadata={"locale-code": "hu"})
    italian: str | None = field(default=None, metadata={"locale-code": "it"})
    japanese: str | None = field(default=None, metadata={"locale-code": "ja"})
    korean: str | None = field(default=None, metadata={"locale-code": "ko"})
    lithuanian: str | None = field(default=None, metadata={"locale-code": "lt"})
    norwegian: str | None = field(default=None, metadata={"locale-code": "no"})
    polish: str | None = field(default=None, metadata={"locale-code": "pl"})
    portuguese_brazilian: str | None = field(default=None, metadata={"locale-code": "pt-BR"})
    romanian_romania: str | None = field(default=None, metadata={"locale-code": "ro"})
    russian: str | None = field(default=None, metadata={"locale-code": "ru"})
    spanish: str | None = field(default=None, metadata={"locale-code": "es-ES"})
    swedish: str | None = field(default=None, metadata={"locale-code": "sv-SE"})
    thai: str | None = field(default=None, metadata={"locale-code": "th"})
    turkish: str | None = field(default=None, metadata={"locale-code": "tr"})
    ukrainian: str | None = field(default=None, metadata={"locale-code": "uk"})
    vietnamese: str | None = field(default=None, metadata={"locale-code": "vi"})

    def __str__(self) -> str:
        return str(self.default)

    def __bool__(self) -> bool:
        return self.default is not None

    def __repr__(self) -> str:
        return f"<{self.__class__.__name__}: default_locale={self.default_locale}, value='{self}'>"

    @cached_property
    def _code_mapping(self) -> dict:
        """Generates a lookup table for this object on demand to allow for value retrieval with locale codes"""
        data = {}
        for attr in self.__attrs_attrs__:
            if attr.name != self.default_locale:
                if code := attr.metadata.get("locale-code"):
                    data[code] = attr.name
        return data

    @property
    def default(self) -> str:
        """The default value based on the CONST default_locale"""
        return getattr(self, self.default_locale)

    def get_locale(self, locale: str) -> str:
        """
        Get the value for the specified locale. Supports locale-codes and locale names.

        Args:
            locale: The locale to fetch

        Returns:
            The localised string, or the default value
        """
        if val := getattr(self, locale, None):
            # Attempt to retrieve an attribute with the specified locale
            return val
        if attr := self._code_mapping.get(locale):
            # assume the locale is a code, and attempt to find an attribute with that code
            if val := getattr(self, attr, None):
                # if the value isn't None, return
                return val

        # no value was found, return default
        return self.default

    @classmethod
    def converter(cls, value: str | None) -> "LocalisedField":
        if isinstance(value, LocalisedField):
            return value
        obj = cls()
        if value:
            obj.__setattr__(obj.default_locale, str(value))

        return obj

    @default_locale.validator
    def _default_locale_validator(self, _, value: str) -> None:
        try:
            getattr(self, value)
        except AttributeError:
            raise ValueError(f"`{value}` is not a supported default localisation") from None

    def as_dict(self) -> str:
        return str(self)

    def to_locale_dict(self) -> dict:
        data = {}
        for attr in self.__attrs_attrs__:
            if attr.name != self.default_locale:
                if "locale-code" in attr.metadata:
                    if val := getattr(self, attr.name):
                        data[attr.metadata["locale-code"]] = val

        if not data:
            data = None  # handle discord being stupid
        return data

default() property

The default value based on the CONST default_locale

Source code in naff/models/naff/localisation.py
69
70
71
72
@property
def default(self) -> str:
    """The default value based on the CONST default_locale"""
    return getattr(self, self.default_locale)

get_locale(locale)

Get the value for the specified locale. Supports locale-codes and locale names.

Parameters:

Name Type Description Default
locale str

The locale to fetch

required

Returns:

Type Description
str

The localised string, or the default value

Source code in naff/models/naff/localisation.py
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
def get_locale(self, locale: str) -> str:
    """
    Get the value for the specified locale. Supports locale-codes and locale names.

    Args:
        locale: The locale to fetch

    Returns:
        The localised string, or the default value
    """
    if val := getattr(self, locale, None):
        # Attempt to retrieve an attribute with the specified locale
        return val
    if attr := self._code_mapping.get(locale):
        # assume the locale is a code, and attempt to find an attribute with that code
        if val := getattr(self, attr, None):
            # if the value isn't None, return
            return val

    # no value was found, return default
    return self.default

BaseTrigger

Bases: ABC

Source code in naff/models/naff/tasks/triggers.py
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class BaseTrigger(ABC):
    last_call_time: datetime

    def __new__(cls, *args, **kwargs) -> "BaseTrigger":
        new_cls = super().__new__(cls)
        new_cls.last_call_time = datetime.now()
        return new_cls

    def __or__(self, other: "BaseTrigger") -> "OrTrigger":
        return OrTrigger(self, other)

    @abstractmethod
    def next_fire(self) -> datetime | None:
        """
        Return the next datetime to fire on.

        Returns:
            Datetime if one can be determined. If no datetime can be determined, return None

        """
        ...

next_fire() abstractmethod

Return the next datetime to fire on.

Returns:

Type Description
datetime | None

Datetime if one can be determined. If no datetime can be determined, return None

Source code in naff/models/naff/tasks/triggers.py
18
19
20
21
22
23
24
25
26
27
@abstractmethod
def next_fire(self) -> datetime | None:
    """
    Return the next datetime to fire on.

    Returns:
        Datetime if one can be determined. If no datetime can be determined, return None

    """
    ...

Task

Create an asynchronous background tasks. Tasks allow you to run code according to a trigger object.

A task's trigger must inherit from BaseTrigger.

Attributes:

Name Type Description
callback Callable

The function to be called when the trigger is triggered.

trigger BaseTrigger

The trigger object that determines when the task should run.

task Optional[_Task]

The task object that is running the trigger loop.

iteration int

The number of times the task has run.

Source code in naff/models/naff/tasks/task.py
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
class Task:
    """
    Create an asynchronous background tasks. Tasks allow you to run code according to a trigger object.

    A task's trigger must inherit from `BaseTrigger`.

    Attributes:
        callback (Callable): The function to be called when the trigger is triggered.
        trigger (BaseTrigger): The trigger object that determines when the task should run.
        task (Optional[_Task]): The task object that is running the trigger loop.
        iteration (int): The number of times the task has run.

    """

    callback: Callable
    trigger: BaseTrigger
    task: _Task | None
    _stop: asyncio.Event
    iteration: int

    def __init__(self, callback: Callable, trigger: BaseTrigger) -> None:
        self.callback = callback
        self.trigger = trigger
        self._stop = asyncio.Event()
        self.task = None
        self.iteration = 0

    @property
    def started(self) -> bool:
        """Whether the task is started"""
        return self.task is not None

    @property
    def running(self) -> bool:
        """Whether the task is running"""
        return self.task is not None and not self.task.done()

    @property
    def done(self) -> bool:
        """Whether the task is done/finished"""
        return self.task is not None and self.task.done()

    @property
    def next_run(self) -> datetime | None:
        """Get the next datetime this task will run."""
        if not self.running:
            return None

        return self.trigger.next_fire()

    @property
    def delta_until_run(self) -> timedelta | None:
        """Get the time until the next run of this task."""
        if not self.running:
            return None

        next_run = self.next_run
        return next_run - datetime.now() if next_run is not None else None

    def on_error_sentry_hook(self, error: Exception) -> None:
        """A dummy method for naff.ext.sentry to hook"""

    def on_error(self, error: Exception) -> None:
        """Error handler for this task. Called when an exception is raised during execution of the task."""
        self.on_error_sentry_hook(error)
        naff.Client.default_error_handler("Task", error)

    async def __call__(self) -> None:
        try:
            if inspect.iscoroutinefunction(self.callback):
                val = await self.callback()
            else:
                val = self.callback()

            if isinstance(val, BaseTrigger):
                self.reschedule(val)
        except Exception as e:
            self.on_error(e)

    def _fire(self, fire_time: datetime) -> None:
        """Called when the task is being fired."""
        self.trigger.last_call_time = fire_time
        asyncio.create_task(self())
        self.iteration += 1

    async def _task_loop(self) -> None:
        """The main task loop to fire the task at the specified time based on triggers configured."""
        while not self._stop.is_set():
            fire_time = self.trigger.next_fire()
            if fire_time is None:
                return self.stop()

            try:
                await asyncio.wait_for(self._stop.wait(), max(0.0, (fire_time - datetime.now()).total_seconds()))
            except asyncio.TimeoutError:
                pass
            else:
                return None

            self._fire(fire_time)

    def start(self) -> None:
        """Start this task."""
        try:
            self._stop.clear()
            self.task = asyncio.create_task(self._task_loop())
        except RuntimeError:
            logger.error(
                "Unable to start task without a running event loop! We recommend starting tasks within an `on_startup` event."
            )

    def stop(self) -> None:
        """End this task."""
        self._stop.set()
        if self.task:
            self.task.cancel()

    def restart(self) -> None:
        """Restart this task."""
        self.stop()
        self.start()

    def reschedule(self, trigger: BaseTrigger) -> None:
        """
        Change the trigger being used by this task.

        Args:
            trigger: The new Trigger to use

        """
        self.trigger = trigger
        self.restart()

    @classmethod
    def create(cls, trigger: BaseTrigger) -> Callable[[Callable], "Task"]:
        """
        A decorator to create a task.

        Example:
            ```python
            @Task.create(IntervalTrigger(minutes=5))
            async def my_task():
                print("It's been 5 minutes!")

            @listen()
            async def on_startup():
                my_task.start()
            ```

        Args:
            trigger: The trigger to use for this task

        """

        def wrapper(func: Callable) -> "Task":
            return cls(func, trigger)

        return wrapper

started() property

Whether the task is started

Source code in naff/models/naff/tasks/task.py
43
44
45
46
@property
def started(self) -> bool:
    """Whether the task is started"""
    return self.task is not None

running() property

Whether the task is running

Source code in naff/models/naff/tasks/task.py
48
49
50
51
@property
def running(self) -> bool:
    """Whether the task is running"""
    return self.task is not None and not self.task.done()

done() property

Whether the task is done/finished

Source code in naff/models/naff/tasks/task.py
53
54
55
56
@property
def done(self) -> bool:
    """Whether the task is done/finished"""
    return self.task is not None and self.task.done()

next_run() property

Get the next datetime this task will run.

Source code in naff/models/naff/tasks/task.py
58
59
60
61
62
63
64
@property
def next_run(self) -> datetime | None:
    """Get the next datetime this task will run."""
    if not self.running:
        return None

    return self.trigger.next_fire()

delta_until_run() property

Get the time until the next run of this task.

Source code in naff/models/naff/tasks/task.py
66
67
68
69
70
71
72
73
@property
def delta_until_run(self) -> timedelta | None:
    """Get the time until the next run of this task."""
    if not self.running:
        return None

    next_run = self.next_run
    return next_run - datetime.now() if next_run is not None else None

on_error_sentry_hook(error)

A dummy method for naff.ext.sentry to hook

Source code in naff/models/naff/tasks/task.py
75
76
def on_error_sentry_hook(self, error: Exception) -> None:
    """A dummy method for naff.ext.sentry to hook"""

on_error(error)

Error handler for this task. Called when an exception is raised during execution of the task.

Source code in naff/models/naff/tasks/task.py
78
79
80
81
def on_error(self, error: Exception) -> None:
    """Error handler for this task. Called when an exception is raised during execution of the task."""
    self.on_error_sentry_hook(error)
    naff.Client.default_error_handler("Task", error)

start()

Start this task.

Source code in naff/models/naff/tasks/task.py
117
118
119
120
121
122
123
124
125
def start(self) -> None:
    """Start this task."""
    try:
        self._stop.clear()
        self.task = asyncio.create_task(self._task_loop())
    except RuntimeError:
        logger.error(
            "Unable to start task without a running event loop! We recommend starting tasks within an `on_startup` event."
        )

stop()

End this task.

Source code in naff/models/naff/tasks/task.py
127
128
129
130
131
def stop(self) -> None:
    """End this task."""
    self._stop.set()
    if self.task:
        self.task.cancel()

restart()

Restart this task.

Source code in naff/models/naff/tasks/task.py
133
134
135
136
def restart(self) -> None:
    """Restart this task."""
    self.stop()
    self.start()

reschedule(trigger)

Change the trigger being used by this task.

Parameters:

Name Type Description Default
trigger BaseTrigger

The new Trigger to use

required
Source code in naff/models/naff/tasks/task.py
138
139
140
141
142
143
144
145
146
147
def reschedule(self, trigger: BaseTrigger) -> None:
    """
    Change the trigger being used by this task.

    Args:
        trigger: The new Trigger to use

    """
    self.trigger = trigger
    self.restart()

create(trigger) classmethod

A decorator to create a task.

Example
1
2
3
4
5
6
7
@Task.create(IntervalTrigger(minutes=5))
async def my_task():
    print("It's been 5 minutes!")

@listen()
async def on_startup():
    my_task.start()

Parameters:

Name Type Description Default
trigger BaseTrigger

The trigger to use for this task

required
Source code in naff/models/naff/tasks/task.py
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
@classmethod
def create(cls, trigger: BaseTrigger) -> Callable[[Callable], "Task"]:
    """
    A decorator to create a task.

    Example:
        ```python
        @Task.create(IntervalTrigger(minutes=5))
        async def my_task():
            print("It's been 5 minutes!")

        @listen()
        async def on_startup():
            my_task.start()
        ```

    Args:
        trigger: The trigger to use for this task

    """

    def wrapper(func: Callable) -> "Task":
        return cls(func, trigger)

    return wrapper

IntervalTrigger

Bases: BaseTrigger

Trigger the task every set interval.

Attributes:

Name Type Description
seconds Union[int, float]

How many seconds between intervals

minutes Union[int, float]

How many minutes between intervals

hours Union[int, float]

How many hours between intervals

days Union[int, float]

How many days between intervals

weeks Union[int, float]

How many weeks between intervals

Source code in naff/models/naff/tasks/triggers.py
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
class IntervalTrigger(BaseTrigger):
    """
    Trigger the task every set interval.

    Attributes:
        seconds Union[int, float]: How many seconds between intervals
        minutes Union[int, float]: How many minutes between intervals
        hours Union[int, float]: How many hours between intervals
        days Union[int, float]: How many days between intervals
        weeks Union[int, float]: How many weeks between intervals

    """

    _t = int | float

    def __init__(self, seconds: _t = 0, minutes: _t = 0, hours: _t = 0, days: _t = 0, weeks: _t = 0) -> None:
        self.delta = timedelta(days=days, seconds=seconds, minutes=minutes, hours=hours, weeks=weeks)

        # lazy check for negatives
        if (datetime.now() + self.delta) < datetime.now():
            raise ValueError("Interval values must result in a time in the future!")

    def next_fire(self) -> datetime | None:
        return self.last_call_time + self.delta

DateTrigger

Bases: BaseTrigger

Trigger the task once, when the specified datetime is reached.

Attributes:

Name Type Description
target_datetime datetime

A datetime representing the date/time to run this task

Source code in naff/models/naff/tasks/triggers.py
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
class DateTrigger(BaseTrigger):
    """
    Trigger the task once, when the specified datetime is reached.

    Attributes:
        target_datetime datetime: A datetime representing the date/time to run this task

    """

    def __init__(self, target_datetime: datetime) -> None:
        self.target = target_datetime

    def next_fire(self) -> datetime | None:
        if datetime.now() < self.target:
            return self.target
        return None

TimeTrigger

Bases: BaseTrigger

Trigger the task every day, at a specified (24 hour clock) time.

Attributes:

Name Type Description
hour int

The hour of the day (24 hour clock)

minute int

The minute of the hour

seconds int

The seconds of the minute

utc bool

If this time is in UTC

Source code in naff/models/naff/tasks/triggers.py
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
class TimeTrigger(BaseTrigger):
    """
    Trigger the task every day, at a specified (24 hour clock) time.

    Attributes:
        hour int: The hour of the day (24 hour clock)
        minute int: The minute of the hour
        seconds int: The seconds of the minute
        utc bool: If this time is in UTC

    """

    def __init__(self, hour: int = 0, minute: int = 0, seconds: int = 0, utc: bool = True) -> None:
        self.target_time = (hour, minute, seconds)
        self.tz = timezone.utc if utc else None

    def next_fire(self) -> datetime | None:
        now = datetime.now()
        target = datetime(
            now.year, now.month, now.day, self.target_time[0], self.target_time[1], self.target_time[2], tzinfo=self.tz
        )
        if target.tzinfo == timezone.utc:
            target = target.astimezone(now.tzinfo)
            target = target.replace(tzinfo=None)

        if target <= self.last_call_time:
            target += timedelta(days=1)
        return target

OrTrigger

Bases: BaseTrigger

Trigger a task when any sub-trigger is fulfilled.

Source code in naff/models/naff/tasks/triggers.py
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
class OrTrigger(BaseTrigger):
    """Trigger a task when any sub-trigger is fulfilled."""

    def __init__(self, *trigger: BaseTrigger) -> None:
        self.triggers: list[BaseTrigger] = list(trigger)

    def _get_delta(self, d: BaseTrigger) -> timedelta:
        next_fire = d.next_fire()
        if not next_fire:
            return timedelta.max
        return abs(next_fire - self.last_call_time)

    def __or__(self, other: "BaseTrigger") -> "OrTrigger":
        self.triggers.append(other)
        return self

    def next_fire(self) -> datetime | None:
        if len(self.triggers) == 1:
            return self.triggers[0].next_fire()
        trigger = min(self.triggers, key=self._get_delta)
        return trigger.next_fire()

Wait

Class for waiting for a future event to happen. Internally used by wait_for.

Source code in naff/models/naff/wait.py
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
class Wait:
    """Class for waiting for a future event to happen. Internally used by wait_for."""

    def __init__(self, event: str, checks: Optional[Callable[..., bool]], future: Future) -> None:
        self.event = event
        self.checks = checks
        self.future = future

    def __call__(self, *args, **kwargs) -> bool:
        if self.future.cancelled():
            return True

        if self.checks:
            try:
                check_result = self.checks(*args, **kwargs)
            except Exception as exc:
                self.future.set_exception(exc)
                return True
        else:
            check_result = True

        if check_result:
            self.future.set_result(*args, **kwargs)
            return True

        return False

AsyncIterator

Bases: _AsyncIterator, ABC

Source code in naff/models/misc/iterator.py
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
class AsyncIterator(_AsyncIterator, ABC):
    def __init__(self, limit: int = 50) -> None:
        self._queue: asyncio.Queue = asyncio.Queue()
        """The queue of items in the iterator"""

        self._limit: int = limit if limit else MISSING
        """the limit of items to retrieve"""

        self.last: Absent[Any] = MISSING
        """The last item retrieved"""

        self._retrieved_objects: List = []
        """All items this iterator has retrieved"""

    @property
    def _continue(self) -> bool:
        """Whether iteration should continue. Returns False if the limit has been reached."""
        if not self._limit:
            return True
        return not len(self._retrieved_objects) >= self._limit

    @property
    def get_limit(self) -> int:
        """Get how the maximum number of items that should be retrieved."""
        return min(self._limit - len(self._retrieved_objects), 100) if self._limit else 100

    @property
    def total_retrieved(self) -> int:
        """Get the total number of objects this iterator has retrieved."""
        return len(self._retrieved_objects)

    async def add_object(self, obj) -> None:
        """Add an object to iterator's queue."""
        return await self._queue.put(obj)

    @abstractmethod
    async def fetch(self) -> list:
        """
        Fetch additional objects.

        Your implementation of this method *must* return a list of objects.
        If no more objects are available, raise QueueEmpty

        Returns:
            List of objects

        Raises:
            QueueEmpty:  when no more objects are available.

        """
        ...

    async def _get_items(self) -> None:
        if self._continue:
            data = await self.fetch()
            [await self.add_object(obj) for obj in data]
        else:
            raise QueueEmpty

    async def __anext__(self) -> Any:
        try:
            if self._queue.empty():
                await self._get_items()
            self.last = self._queue.get_nowait()

            # add the message to the already retrieved objects, so that the search function works when calling it multiple times
            self._retrieved_objects.append(self.last)

            return self.last
        except QueueEmpty as e:
            raise StopAsyncIteration from e

    async def flatten(self) -> List:
        """Flatten this iterator into a list of objects."""
        return [elem async for elem in self]

    async def search(self, target_id: "snowflake.Snowflake_Type") -> bool:
        """Search the iterator for an object with the given ID."""
        target_id = snowflake.to_snowflake(target_id)

        if target_id in [o.id for o in self._retrieved_objects]:
            return True

        async for o in self:
            if o.id == target_id:
                return True
        return False

last: Absent[Any] = MISSING instance-attribute

The last item retrieved

get_limit() property

Get how the maximum number of items that should be retrieved.

Source code in naff/models/misc/iterator.py
34
35
36
37
@property
def get_limit(self) -> int:
    """Get how the maximum number of items that should be retrieved."""
    return min(self._limit - len(self._retrieved_objects), 100) if self._limit else 100

total_retrieved() property

Get the total number of objects this iterator has retrieved.

Source code in naff/models/misc/iterator.py
39
40
41
42
@property
def total_retrieved(self) -> int:
    """Get the total number of objects this iterator has retrieved."""
    return len(self._retrieved_objects)

add_object(obj) async

Add an object to iterator's queue.

Source code in naff/models/misc/iterator.py
44
45
46
async def add_object(self, obj) -> None:
    """Add an object to iterator's queue."""
    return await self._queue.put(obj)

fetch() async abstractmethod

Fetch additional objects.

Your implementation of this method must return a list of objects. If no more objects are available, raise QueueEmpty

Returns:

Type Description
list

List of objects

Raises:

Type Description
QueueEmpty

when no more objects are available.

Source code in naff/models/misc/iterator.py
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
@abstractmethod
async def fetch(self) -> list:
    """
    Fetch additional objects.

    Your implementation of this method *must* return a list of objects.
    If no more objects are available, raise QueueEmpty

    Returns:
        List of objects

    Raises:
        QueueEmpty:  when no more objects are available.

    """
    ...

flatten() async

Flatten this iterator into a list of objects.

Source code in naff/models/misc/iterator.py
85
86
87
async def flatten(self) -> List:
    """Flatten this iterator into a list of objects."""
    return [elem async for elem in self]

search(target_id) async

Search the iterator for an object with the given ID.

Source code in naff/models/misc/iterator.py
89
90
91
92
93
94
95
96
97
98
99
async def search(self, target_id: "snowflake.Snowflake_Type") -> bool:
    """Search the iterator for an object with the given ID."""
    target_id = snowflake.to_snowflake(target_id)

    if target_id in [o.id for o in self._retrieved_objects]:
        return True

    async for o in self:
        if o.id == target_id:
            return True
    return False

API

ConnectionState

Source code in naff/api/gateway/state.py
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
@define(kw_only=False)
class ConnectionState:
    client: "Client"
    """The bot's client"""
    intents: Intents
    """The event intents in use"""
    shard_id: int
    """The shard ID of this state"""
    _shard_ready: asyncio.Event = field(default=None)
    """Indicates that this state is now ready"""

    gateway: Absent[GatewayClient] = MISSING
    """The websocket connection for the Discord Gateway."""

    start_time: Absent[datetime] = MISSING
    """The DateTime the bot started at"""

    gateway_url: str = MISSING
    """The URL that the gateway should connect to."""

    gateway_started: asyncio.Event = asyncio.Event()
    """Event to check if the gateway has been started."""

    _shard_task: asyncio.Task | None = None

    def __attrs_post_init__(self, *args, **kwargs) -> None:
        self._shard_ready = asyncio.Event()

    @property
    def latency(self) -> float:
        """Returns the latency of the websocket connection."""
        return self.gateway.average_latency

    @property
    def average_latency(self) -> float:
        """Returns the average latency of the websocket connection."""
        return self.gateway.average_latency

    @property
    def presence(self) -> dict:
        """Returns the presence of the bot."""
        return {
            "status": self.client._status,
            "activities": [self.client._activity.to_dict()] if self.client._activity else [],
        }

    async def start(self) -> None:
        """Connect to the Discord Gateway."""
        self.gateway_url = await self.client.http.get_gateway()

        logger.debug(f"Starting Shard ID {self.shard_id}")
        self.start_time = datetime.now()
        self._shard_task = asyncio.create_task(self._ws_connect())

        self.gateway_started.set()

        # Historically this method didn't return until the connection closed
        # so we need to wait for the task to exit.
        await self._shard_task

    async def stop(self) -> None:
        """Disconnect from the Discord Gateway."""
        logger.debug(f"Shutting down shard ID {self.shard_id}")
        if self.gateway is not None:
            self.gateway.close()
            self.gateway = None

        if self._shard_task is not None:
            await self._shard_task
            self._shard_task = None

        self.gateway_started.clear()

    def clear_ready(self) -> None:
        """Clear the ready event."""
        self._shard_ready.clear()
        self.client._ready.clear()  # noinspection PyProtectedMember

    async def _ws_connect(self) -> None:
        """Connect to the Discord Gateway."""
        logger.info(f"Shard {self.shard_id} is attempting to connect to gateway...")
        try:
            async with GatewayClient(self, (self.shard_id, self.client.total_shards)) as self.gateway:
                try:
                    await self.gateway.run()
                finally:
                    self._shard_ready.clear()
                    if self.client.total_shards == 1:
                        self.client.dispatch(events.Disconnect())
                    else:
                        self.client.dispatch(events.ShardDisconnect(self.shard_id))

        except WebSocketClosed as ex:
            if ex.code == 4011:
                raise NaffException("Your bot is too large, you must use shards") from None
            elif ex.code == 4013:
                raise NaffException(f"Invalid Intents have been passed: {self.intents}") from None
            elif ex.code == 4014:
                raise NaffException(
                    "You have requested privileged intents that have not been enabled or approved. Check the developer dashboard"
                ) from None
            raise

        except Exception as e:
            self.client.dispatch(events.Disconnect())
            logger.error("".join(traceback.format_exception(type(e), e, e.__traceback__)))

    async def change_presence(
        self, status: Optional[Union[str, Status]] = Status.ONLINE, activity: Absent[Union[Activity, str]] = MISSING
    ) -> None:
        """
        Change the bots presence.

        Args:
            status: The status for the bot to be. i.e. online, afk, etc.
            activity: The activity for the bot to be displayed as doing.

        !!! note
            Bots may only be `playing` `streaming` `listening` `watching` or `competing`, other activity types are likely to fail.

        """
        if activity is not MISSING:
            if activity is None:
                activity = []
            else:
                if not isinstance(activity, Activity):
                    # squash whatever the user passed into an activity
                    activity = Activity.create(name=str(activity))

                if activity.type == ActivityType.STREAMING:
                    if not activity.url:
                        logger.warning("Streaming activity cannot be set without a valid URL attribute")
                elif activity.type not in [
                    ActivityType.GAME,
                    ActivityType.STREAMING,
                    ActivityType.LISTENING,
                    ActivityType.WATCHING,
                    ActivityType.COMPETING,
                ]:
                    logger.warning(f"Activity type `{ActivityType(activity.type).name}` may not be enabled for bots")
        else:
            activity = self.client.activity

        if status:
            if not isinstance(status, Status):
                try:
                    status = Status[status.upper()]
                except KeyError:
                    raise ValueError(f"`{status}` is not a valid status type. Please use the Status enum") from None
        else:
            # in case the user set status to None
            if self.client.status:
                status = self.client.status
            else:
                logger.warning("Status must be set to a valid status type, defaulting to online")
                status = Status.ONLINE

        self.client._status = status
        self.client._activity = activity
        await self.gateway.change_presence(activity.to_dict() if activity else None, status)

    def get_voice_state(self, guild_id: "Snowflake_Type") -> Optional["naff.ActiveVoiceState"]:
        """
        Get the bot's voice state for a guild.

        Args:
            guild_id: The target guild's id.

        Returns:
            The bot's voice state for the guild if connected, otherwise None.

        """
        return self.client.cache.get_bot_voice_state(guild_id)

    async def voice_connect(
        self, guild_id: "Snowflake_Type", channel_id: "Snowflake_Type", muted: bool = False, deafened: bool = False
    ) -> "naff.ActiveVoiceState":
        """
        Connect to a voice channel.

        Args:
            guild_id: id of the guild the voice channel is in.
            channel_id: id of the voice channel client wants to join.
            muted: Whether the bot should be muted when connected.
            deafened: Whether the bot should be deafened when connected.

        Returns:
            The new active voice state on successfully connection.

        """
        voice_state = naff.ActiveVoiceState(
            client=self.client, guild_id=guild_id, channel_id=channel_id, self_mute=muted, self_deaf=deafened
        )
        await voice_state.connect()
        self.client.cache.place_bot_voice_state(voice_state)
        return voice_state

client: Client class-attribute

The bot's client

intents: Intents class-attribute

The event intents in use

shard_id: int class-attribute

The shard ID of this state

gateway: Absent[GatewayClient] = MISSING class-attribute

The websocket connection for the Discord Gateway.

start_time: Absent[datetime] = MISSING class-attribute

The DateTime the bot started at

gateway_url: str = MISSING class-attribute

The URL that the gateway should connect to.

gateway_started: asyncio.Event = asyncio.Event() class-attribute

Event to check if the gateway has been started.

latency() property

Returns the latency of the websocket connection.

Source code in naff/api/gateway/state.py
49
50
51
52
@property
def latency(self) -> float:
    """Returns the latency of the websocket connection."""
    return self.gateway.average_latency

average_latency() property

Returns the average latency of the websocket connection.

Source code in naff/api/gateway/state.py
54
55
56
57
@property
def average_latency(self) -> float:
    """Returns the average latency of the websocket connection."""
    return self.gateway.average_latency

presence() property

Returns the presence of the bot.

Source code in naff/api/gateway/state.py
59
60
61
62
63
64
65
@property
def presence(self) -> dict:
    """Returns the presence of the bot."""
    return {
        "status": self.client._status,
        "activities": [self.client._activity.to_dict()] if self.client._activity else [],
    }

start() async

Connect to the Discord Gateway.

Source code in naff/api/gateway/state.py
67
68
69
70
71
72
73
74
75
76
77
78
79
async def start(self) -> None:
    """Connect to the Discord Gateway."""
    self.gateway_url = await self.client.http.get_gateway()

    logger.debug(f"Starting Shard ID {self.shard_id}")
    self.start_time = datetime.now()
    self._shard_task = asyncio.create_task(self._ws_connect())

    self.gateway_started.set()

    # Historically this method didn't return until the connection closed
    # so we need to wait for the task to exit.
    await self._shard_task

stop() async

Disconnect from the Discord Gateway.

Source code in naff/api/gateway/state.py
81
82
83
84
85
86
87
88
89
90
91
92
async def stop(self) -> None:
    """Disconnect from the Discord Gateway."""
    logger.debug(f"Shutting down shard ID {self.shard_id}")
    if self.gateway is not None:
        self.gateway.close()
        self.gateway = None

    if self._shard_task is not None:
        await self._shard_task
        self._shard_task = None

    self.gateway_started.clear()

clear_ready()

Clear the ready event.

Source code in naff/api/gateway/state.py
94
95
96
97
def clear_ready(self) -> None:
    """Clear the ready event."""
    self._shard_ready.clear()
    self.client._ready.clear()  # noinspection PyProtectedMember

change_presence(status=Status.ONLINE, activity=MISSING) async

Change the bots presence.

Parameters:

Name Type Description Default
status Optional[Union[str, Status]]

The status for the bot to be. i.e. online, afk, etc.

Status.ONLINE
activity Absent[Union[Activity, str]]

The activity for the bot to be displayed as doing.

MISSING

Note

Bots may only be playing streaming listening watching or competing, other activity types are likely to fail.

Source code in naff/api/gateway/state.py
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
async def change_presence(
    self, status: Optional[Union[str, Status]] = Status.ONLINE, activity: Absent[Union[Activity, str]] = MISSING
) -> None:
    """
    Change the bots presence.

    Args:
        status: The status for the bot to be. i.e. online, afk, etc.
        activity: The activity for the bot to be displayed as doing.

    !!! note
        Bots may only be `playing` `streaming` `listening` `watching` or `competing`, other activity types are likely to fail.

    """
    if activity is not MISSING:
        if activity is None:
            activity = []
        else:
            if not isinstance(activity, Activity):
                # squash whatever the user passed into an activity
                activity = Activity.create(name=str(activity))

            if activity.type == ActivityType.STREAMING:
                if not activity.url:
                    logger.warning("Streaming activity cannot be set without a valid URL attribute")
            elif activity.type not in [
                ActivityType.GAME,
                ActivityType.STREAMING,
                ActivityType.LISTENING,
                ActivityType.WATCHING,
                ActivityType.COMPETING,
            ]:
                logger.warning(f"Activity type `{ActivityType(activity.type).name}` may not be enabled for bots")
    else:
        activity = self.client.activity

    if status:
        if not isinstance(status, Status):
            try:
                status = Status[status.upper()]
            except KeyError:
                raise ValueError(f"`{status}` is not a valid status type. Please use the Status enum") from None
    else:
        # in case the user set status to None
        if self.client.status:
            status = self.client.status
        else:
            logger.warning("Status must be set to a valid status type, defaulting to online")
            status = Status.ONLINE

    self.client._status = status
    self.client._activity = activity
    await self.gateway.change_presence(activity.to_dict() if activity else None, status)

get_voice_state(guild_id)

Get the bot's voice state for a guild.

Parameters:

Name Type Description Default
guild_id Snowflake_Type

The target guild's id.

required

Returns:

Type Description
Optional[ActiveVoiceState]

The bot's voice state for the guild if connected, otherwise None.

Source code in naff/api/gateway/state.py
182
183
184
185
186
187
188
189
190
191
192
193
def get_voice_state(self, guild_id: "Snowflake_Type") -> Optional["naff.ActiveVoiceState"]:
    """
    Get the bot's voice state for a guild.

    Args:
        guild_id: The target guild's id.

    Returns:
        The bot's voice state for the guild if connected, otherwise None.

    """
    return self.client.cache.get_bot_voice_state(guild_id)

voice_connect(guild_id, channel_id, muted=False, deafened=False) async

Connect to a voice channel.

Parameters:

Name Type Description Default
guild_id Snowflake_Type

id of the guild the voice channel is in.

required
channel_id Snowflake_Type

id of the voice channel client wants to join.

required
muted bool

Whether the bot should be muted when connected.

False
deafened bool

Whether the bot should be deafened when connected.

False

Returns:

Type Description
ActiveVoiceState

The new active voice state on successfully connection.

Source code in naff/api/gateway/state.py
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
async def voice_connect(
    self, guild_id: "Snowflake_Type", channel_id: "Snowflake_Type", muted: bool = False, deafened: bool = False
) -> "naff.ActiveVoiceState":
    """
    Connect to a voice channel.

    Args:
        guild_id: id of the guild the voice channel is in.
        channel_id: id of the voice channel client wants to join.
        muted: Whether the bot should be muted when connected.
        deafened: Whether the bot should be deafened when connected.

    Returns:
        The new active voice state on successfully connection.

    """
    voice_state = naff.ActiveVoiceState(
        client=self.client, guild_id=guild_id, channel_id=channel_id, self_mute=muted, self_deaf=deafened
    )
    await voice_state.connect()
    self.client.cache.place_bot_voice_state(voice_state)
    return voice_state

This file outlines the interaction between naff and Discord's Gateway API.

GatewayClient

Bases: WebsocketClient

Abstraction over one gateway connection.

Multiple WebsocketClient instances can be used to implement same-process sharding.

Attributes:

Name Type Description
sequence

The sequence of this connection

session_id

The session ID of this connection

Source code in naff/api/gateway/gateway.py
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
class GatewayClient(WebsocketClient):
    """
    Abstraction over one gateway connection.

    Multiple `WebsocketClient` instances can be used to implement same-process sharding.

    Attributes:
        sequence: The sequence of this connection
        session_id: The session ID of this connection

    """

    def __init__(self, state: "ConnectionState", shard: tuple[int, int]) -> None:
        super().__init__(state)

        self.shard = shard

        self.chunk_cache = {}

        self._trace = []
        self.sequence = None
        self.session_id = None

        self.ws_url = state.gateway_url
        self.ws_resume_url = MISSING

        # This lock needs to be held to send something over the gateway, but is also held when
        # reconnecting. That way there's no race conditions between sending and reconnecting.
        self._race_lock = asyncio.Lock()
        # Then this event is used so that receive() can wait for the reconnecting to complete.
        self._closed = asyncio.Event()

        self._keep_alive = None
        self._kill_bee_gees = asyncio.Event()
        self._last_heartbeat = 0
        self._acknowledged = asyncio.Event()
        self._acknowledged.set()  # Initialize it as set

        self._ready = asyncio.Event()
        self._close_gateway = asyncio.Event()

        # Sanity check, it is extremely important that an instance isn't reused.
        self._entered = False

    async def __aenter__(self: SELF) -> SELF:
        if self._entered:
            raise RuntimeError("An instance of 'WebsocketClient' cannot be re-used!")

        self._entered = True
        self._zlib = zlib.decompressobj()

        self.ws = await self.state.client.http.websocket_connect(self.state.gateway_url)

        hello = await self.receive(force=True)
        self.heartbeat_interval = hello["d"]["heartbeat_interval"] / 1000
        self._closed.set()

        self._keep_alive = asyncio.create_task(self.run_bee_gees())

        await self._identify()

        return self

    async def __aexit__(
        self, exc_type: type[BaseException] | None, exc_val: BaseException | None, traceback: TracebackType | None
    ) -> None:
        # Technically should not be possible in any way, but might as well be safe worst-case.
        self._close_gateway.set()

        try:
            if self._keep_alive is not None:
                self._kill_bee_gees.set()
                try:
                    # Even if we get cancelled that is fine, because then the keep-alive
                    # handler will also be cancelled since we're waiting on it.
                    await self._keep_alive  # Wait for the keep-alive handler to finish
                finally:
                    self._keep_alive = None
        finally:
            if self.ws is not None:
                # We could be cancelled here, it is extremely important that we close the
                # WebSocket either way, hence the try/except.
                try:
                    await self.ws.close(code=1000)
                finally:
                    self.ws = None

    @property
    def average_latency(self) -> float:
        """Get the average latency of the connection."""
        if self.latency:
            return sum(self.latency) / len(self.latency)
        else:
            return float("inf")

    async def run(self) -> None:
        """Start receiving events from the websocket."""
        while True:
            if self._stopping is None:
                self._stopping = asyncio.create_task(self._close_gateway.wait())
            receiving = asyncio.create_task(self.receive())
            done, _ = await asyncio.wait({self._stopping, receiving}, return_when=asyncio.FIRST_COMPLETED)

            if receiving in done:
                # Note that we check for a received message first, because if both completed at
                # the same time, we don't want to discard that message.
                msg = await receiving
            else:
                # This has to be the stopping task, which we join into the current task (even
                # though that doesn't give any meaningful value in the return).
                await self._stopping
                receiving.cancel()
                return

            op = msg.get("op")
            data = msg.get("d")
            seq = msg.get("s")
            event = msg.get("t")

            if seq:
                self.sequence = seq

            if op == OPCODE.DISPATCH:
                asyncio.create_task(self.dispatch_event(data, seq, event))
                continue

            # This may try to reconnect the connection so it is best to wait
            # for it to complete before receiving more - that way there's less
            # possible race conditions to consider.
            await self.dispatch_opcode(data, op)

    async def dispatch_opcode(self, data, op: OPCODE) -> None:
        match op:

            case OPCODE.HEARTBEAT:
                logger.debug("Received heartbeat request from gateway")
                return await self.send_heartbeat()

            case OPCODE.HEARTBEAT_ACK:
                self.latency.append(time.perf_counter() - self._last_heartbeat)

                if self._last_heartbeat != 0 and self.latency[-1] >= 15:
                    logger.warning(
                        f"High Latency! shard ID {self.shard[0]} heartbeat took {self.latency[-1]:.1f}s to be acknowledged!"
                    )
                else:
                    logger.debug(f"❤ Heartbeat acknowledged after {self.latency[-1]:.5f} seconds")

                return self._acknowledged.set()

            case OPCODE.RECONNECT:
                logger.debug("Gateway requested reconnect. Reconnecting...")
                return await self.reconnect(resume=True, url=self.ws_resume_url)

            case OPCODE.INVALIDATE_SESSION:
                logger.warning("Gateway has invalidated session! Reconnecting...")
                return await self.reconnect()

            case _:
                return logger.debug(f"Unhandled OPCODE: {op} = {OPCODE(op).name}")

    async def dispatch_event(self, data, seq, event) -> None:
        match event:
            case "READY":
                self._ready.set()
                self._trace = data.get("_trace", [])
                self.sequence = seq
                self.session_id = data["session_id"]
                self.ws_resume_url = (
                    f"{data['resume_gateway_url']}?encoding=json&v={__api_version__}&compress=zlib-stream"
                )
                logger.info(f"Shard {self.shard[0]} has connected to gateway!")
                logger.debug(f"Session ID: {self.session_id} Trace: {self._trace}")
                # todo: future polls, improve guild caching here. run the debugger. you'll see why
                return self.state.client.dispatch(events.WebsocketReady(data))

            case "RESUMED":
                logger.info(f"Successfully resumed connection! Session_ID: {self.session_id}")
                self.state.client.dispatch(events.Resume())
                return

            case "GUILD_MEMBERS_CHUNK":
                asyncio.create_task(self._process_member_chunk(data.copy()))

            case _:
                # the above events are "special", and are handled by the gateway itself, the rest can be dispatched
                event_name = f"raw_{event.lower()}"
                processor = self.state.client.processors.get(event_name)
                if processor:
                    try:
                        asyncio.create_task(processor(events.RawGatewayEvent(data.copy(), override_name=event_name)))
                    except Exception as ex:
                        logger.error(f"Failed to run event processor for {event_name}: {ex}")
                else:
                    logger.debug(f"No processor for `{event_name}`")

        self.state.client.dispatch(events.RawGatewayEvent(data.copy(), override_name="raw_gateway_event"))
        self.state.client.dispatch(events.RawGatewayEvent(data.copy(), override_name=f"raw_{event.lower()}"))

    def close(self) -> None:
        """Shutdown the websocket connection."""
        self._close_gateway.set()

    async def _identify(self) -> None:
        """Send an identify payload to the gateway."""
        if self.ws is None:
            raise RuntimeError
        payload = {
            "op": OPCODE.IDENTIFY,
            "d": {
                "token": self.state.client.http.token,
                "intents": self.state.intents,
                "shard": self.shard,
                "large_threshold": 250,
                "properties": {"os": sys.platform, "browser": "naff", "device": "naff"},
                "presence": self.state.presence,
            },
            "compress": True,
        }

        serialized = OverriddenJson.dumps(payload)
        await self.ws.send_str(serialized)

        logger.debug(
            f"Shard ID {self.shard[0]} has identified itself to Gateway, requesting intents: {self.state.intents}!"
        )

    async def reconnect(self, *, resume: bool = False, code: int = 1012, url: str | None = None) -> None:
        self.state.clear_ready()
        self._ready.clear()
        await super().reconnect(resume=resume, code=code, url=url)

    async def _resume_connection(self) -> None:
        """Send a resume payload to the gateway."""
        if self.ws is None:
            raise RuntimeError

        payload = {
            "op": OPCODE.RESUME,
            "d": {"token": self.state.client.http.token, "seq": self.sequence, "session_id": self.session_id},
        }

        serialized = OverriddenJson.dumps(payload)
        await self.ws.send_str(serialized)

        logger.debug(f"{self.shard[0]} is attempting to resume a connection")

    async def send_heartbeat(self) -> None:
        await self.send_json({"op": OPCODE.HEARTBEAT, "d": self.sequence}, bypass=True)
        logger.debug(f"❤ Shard {self.shard[0]} is sending a Heartbeat")

    async def change_presence(self, activity=None, status: Status = Status.ONLINE, since=None) -> None:
        """Update the bot's presence status."""
        payload = dict_filter_none(
            {
                "since": int(since if since else time.time() * 1000),
                "activities": [activity] if activity else [],
                "status": status,
                "afk": False,
            }
        )
        await self.send_json({"op": OPCODE.PRESENCE, "d": payload})

    async def request_member_chunks(
        self,
        guild_id: "Snowflake_Type",
        query="",
        *,
        limit,
        user_ids=None,
        presences=False,
        nonce=None,
    ) -> None:
        payload = {
            "op": OPCODE.REQUEST_MEMBERS,
            "d": dict_filter_none(
                {
                    "guild_id": guild_id,
                    "presences": presences,
                    "limit": limit,
                    "nonce": nonce,
                    "user_ids": user_ids,
                    "query": query,
                }
            ),
        }
        await self.send_json(payload)

    async def _process_member_chunk(self, chunk: dict) -> None:

        guild = self.state.client.cache.get_guild(to_snowflake(chunk.get("guild_id")))
        if guild:
            return asyncio.create_task(guild.process_member_chunk(chunk))
        raise ValueError(f"No guild exists for {chunk.get('guild_id')}")

    async def voice_state_update(
        self, guild_id: "Snowflake_Type", channel_id: "Snowflake_Type", muted: bool = False, deafened: bool = False
    ) -> None:
        """Update the bot's voice state."""
        payload = {
            "op": OPCODE.VOICE_STATE,
            "d": {"guild_id": guild_id, "channel_id": channel_id, "self_mute": muted, "self_deaf": deafened},
        }
        await self.send_json(payload)

average_latency() property

Get the average latency of the connection.

Source code in naff/api/gateway/gateway.py
131
132
133
134
135
136
137
@property
def average_latency(self) -> float:
    """Get the average latency of the connection."""
    if self.latency:
        return sum(self.latency) / len(self.latency)
    else:
        return float("inf")

run() async

Start receiving events from the websocket.

Source code in naff/api/gateway/gateway.py
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
async def run(self) -> None:
    """Start receiving events from the websocket."""
    while True:
        if self._stopping is None:
            self._stopping = asyncio.create_task(self._close_gateway.wait())
        receiving = asyncio.create_task(self.receive())
        done, _ = await asyncio.wait({self._stopping, receiving}, return_when=asyncio.FIRST_COMPLETED)

        if receiving in done:
            # Note that we check for a received message first, because if both completed at
            # the same time, we don't want to discard that message.
            msg = await receiving
        else:
            # This has to be the stopping task, which we join into the current task (even
            # though that doesn't give any meaningful value in the return).
            await self._stopping
            receiving.cancel()
            return

        op = msg.get("op")
        data = msg.get("d")
        seq = msg.get("s")
        event = msg.get("t")

        if seq:
            self.sequence = seq

        if op == OPCODE.DISPATCH:
            asyncio.create_task(self.dispatch_event(data, seq, event))
            continue

        # This may try to reconnect the connection so it is best to wait
        # for it to complete before receiving more - that way there's less
        # possible race conditions to consider.
        await self.dispatch_opcode(data, op)

close()

Shutdown the websocket connection.

Source code in naff/api/gateway/gateway.py
243
244
245
def close(self) -> None:
    """Shutdown the websocket connection."""
    self._close_gateway.set()

change_presence(activity=None, status=Status.ONLINE, since=None) async

Update the bot's presence status.

Source code in naff/api/gateway/gateway.py
295
296
297
298
299
300
301
302
303
304
305
async def change_presence(self, activity=None, status: Status = Status.ONLINE, since=None) -> None:
    """Update the bot's presence status."""
    payload = dict_filter_none(
        {
            "since": int(since if since else time.time() * 1000),
            "activities": [activity] if activity else [],
            "status": status,
            "afk": False,
        }
    )
    await self.send_json({"op": OPCODE.PRESENCE, "d": payload})

voice_state_update(guild_id, channel_id, muted=False, deafened=False) async

Update the bot's voice state.

Source code in naff/api/gateway/gateway.py
339
340
341
342
343
344
345
346
347
async def voice_state_update(
    self, guild_id: "Snowflake_Type", channel_id: "Snowflake_Type", muted: bool = False, deafened: bool = False
) -> None:
    """Update the bot's voice state."""
    payload = {
        "op": OPCODE.VOICE_STATE,
        "d": {"guild_id": guild_id, "channel_id": channel_id, "self_mute": muted, "self_deaf": deafened},
    }
    await self.send_json(payload)

VoiceGateway

Bases: WebsocketClient

Source code in naff/api/voice/voice_gateway.py
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
class VoiceGateway(WebsocketClient):
    guild_id: str
    heartbeat_interval: int
    session_id: str
    token: str
    encryptor: Encryption

    ssrc: int
    me_ip: str
    me_port: int
    voice_ip: str
    voice_port: int
    voice_modes: list[str]
    selected_mode: str
    socket: socket.socket
    ready: Event

    def __init__(self, state, voice_state: dict, voice_server: dict) -> None:
        super().__init__(state)

        self._voice_server_update = asyncio.Event()
        self.ws_url = f"wss://{voice_server['endpoint']}?v=4"
        self.session_id = voice_state["session_id"]
        self.token = voice_server["token"]
        self.guild_id = voice_server["guild_id"]

        self.sock_sequence = 0
        self.timestamp = 0
        self.ready = Event()
        self.cond = None

    async def wait_until_ready(self) -> None:
        await asyncio.to_thread(self.ready.wait)

    async def run(self) -> None:
        """Start receiving events from the websocket."""
        while True:
            if self._stopping is None:
                self._stopping = asyncio.create_task(self._close_gateway.wait())
            receiving = asyncio.create_task(self.receive())
            done, _ = await asyncio.wait({self._stopping, receiving}, return_when=asyncio.FIRST_COMPLETED)

            if receiving in done:
                # Note that we check for a received message first, because if both completed at
                # the same time, we don't want to discard that message.
                msg = await receiving
            else:
                # This has to be the stopping task, which we join into the current task (even
                # though that doesn't give any meaningful value in the return).
                await self._stopping
                receiving.cancel()
                return

            op = msg.get("op")
            data = msg.get("d")
            seq = msg.get("s")

            if seq:
                self.sequence = seq

            # This may try to reconnect the connection so it is best to wait
            # for it to complete before receiving more - that way there's less
            # possible race conditions to consider.
            await self.dispatch_opcode(data, op)

    async def receive(self, force=False) -> str:
        buffer = bytearray()

        while True:
            if not force:
                await self._closed.wait()

            resp = await self.ws.receive()

            if resp.type == WSMsgType.CLOSE:
                logger.debug(f"Disconnecting from voice gateway! Reason: {resp.data}::{resp.extra}")
                if resp.data in (4006, 4009, 4014, 4015):
                    # these are all recoverable close codes, anything else means we're foobared
                    # codes: session expired, session timeout, disconnected, server crash
                    self.ready.clear()
                    # docs state only resume on 4015
                    await self.reconnect(resume=resp.data == 4015)
                    continue
                raise VoiceWebSocketClosed(resp.data)

            elif resp.type is WSMsgType.CLOSED:
                if force:
                    raise RuntimeError("Discord unexpectedly closed the underlying socket during force receive!")

                if not self._closed.is_set():
                    # Because we are waiting for the even before we receive, this shouldn't be
                    # possible - the CLOSING message should be returned instead. Either way, if this
                    # is possible after all we can just wait for the event to be set.
                    await self._closed.wait()
                else:
                    # This is an odd corner-case where the underlying socket connection was closed
                    # unexpectedly without communicating the WebSocket closing handshake. We'll have
                    # to reconnect ourselves.
                    await self.reconnect(resume=True)

            elif resp.type is WSMsgType.CLOSING:
                if force:
                    raise RuntimeError("WebSocket is unexpectedly closing during force receive!")

                # This happens when the keep-alive handler is reconnecting the connection even
                # though we waited for the event before hand, because it got to run while we waited
                # for data to come in. We can just wait for the event again.
                await self._closed.wait()
                continue

            if resp.data is None:
                continue

            if isinstance(resp.data, bytes):
                buffer.extend(resp.data)

                if len(resp.data) < 4 or resp.data[-4:] != b"\x00\x00\xff\xff":
                    # message isn't complete yet, wait
                    continue

                msg = self._zlib.decompress(buffer)
                msg = msg.decode("utf-8")
            else:
                msg = resp.data

            try:
                msg = OverriddenJson.loads(msg)
            except Exception as e:
                logger.error(e)

            return msg

    async def dispatch_opcode(self, data, op) -> None:
        match op:
            case OP.HEARTBEAT_ACK:
                self.latency.append(time.perf_counter() - self._last_heartbeat)

                if self._last_heartbeat != 0 and self.latency[-1] >= 15:
                    logger.warning(f"High Latency! Voice heartbeat took {self.latency[-1]:.1f}s to be acknowledged!")
                else:
                    logger.debug(f"❤ Heartbeat acknowledged after {self.latency[-1]:.5f} seconds")

                return self._acknowledged.set()

            case OP.READY:
                logger.debug("Discord send VC Ready! Establishing a socket connection...")
                self.voice_ip = data["ip"]
                self.voice_port = data["port"]
                self.ssrc = data["ssrc"]
                self.voice_modes = [mode for mode in data["modes"] if mode in Encryption.SUPPORTED]

                if len(self.voice_modes) == 0:
                    logger.critical("NO VOICE ENCRYPTION MODES SHARED WITH GATEWAY!")

                await self.establish_voice_socket()

            case OP.SESSION_DESCRIPTION:
                logger.info(f"Voice connection established; using {data['mode']}")
                self.encryptor = Encryption(data["secret_key"])
                self.ready.set()
                if self.cond:
                    with self.cond:
                        self.cond.notify()

            case _:
                return logger.debug(f"Unhandled OPCODE: {op} = {data = }")

    async def reconnect(self, *, resume: bool = False, code: int = 1012) -> None:
        async with self._race_lock:
            self._closed.clear()

            if self.ws is not None:
                await self.ws.close(code=code)

            self.ws = None

            if not resume:
                logger.debug("Waiting for updated server information...")
                try:
                    await asyncio.wait_for(self._voice_server_update.wait(), timeout=5)
                except asyncio.TimeoutError:
                    self._kill_bee_gees.set()
                    self.close()
                    logger.debug("Terminating VoiceGateway due to disconnection")
                    return

                self._voice_server_update.clear()

            self.ws = await self.state.client.http.websocket_connect(self.ws_url)

            try:
                hello = await self.receive(force=True)
                self.heartbeat_interval = hello["d"]["heartbeat_interval"] / 1000
            except RuntimeError:
                # sometimes the initial connection fails with voice gateways, handle that
                return await self.reconnect(resume=resume, code=code)

            if not resume:
                await self._identify()
            else:
                await self._resume_connection()

            self._closed.set()
            self._acknowledged.set()

    async def _resume_connection(self) -> None:
        if self.ws is None:
            raise RuntimeError

        payload = {
            "op": OP.RESUME,
            "d": {"server_id": self.guild_id, "session_id": self.session_id, "token": self.token},
        }
        await self.ws.send_json(payload)

    async def establish_voice_socket(self) -> None:
        """Establish the socket connection to discord"""
        logger.debug("IP Discovery in progress...")

        self.socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        self.socket.setblocking(False)

        packet = bytearray(70)
        struct.pack_into(">H", packet, 0, 1)  # 1 = Send
        struct.pack_into(">H", packet, 2, 70)  # 70 = Length
        struct.pack_into(">I", packet, 4, self.ssrc)

        self.socket.sendto(packet, (self.voice_ip, self.voice_port))
        resp = await self.loop.sock_recv(self.socket, 70)
        logger.debug(f"Voice Initial Response Received: {resp}")

        ip_start = 4
        ip_end = resp.index(0, ip_start)
        self.me_ip = resp[ip_start:ip_end].decode("ascii")

        self.me_port = struct.unpack_from(">H", resp, len(resp) - 2)[0]
        logger.debug(f"IP Discovered: {self.me_ip} #{self.me_port}")

        await self._select_protocol()

    def generate_packet(self, data: bytes) -> bytes:
        """Generate a packet to be sent to the voice socket."""
        header = bytearray(12)
        header[0] = 0x80
        header[1] = 0x78

        struct.pack_into(">H", header, 2, self.sock_sequence)
        struct.pack_into(">I", header, 4, self.timestamp)
        struct.pack_into(">I", header, 8, self.ssrc)

        return self.encryptor.encrypt(self.voice_modes[0], header, data)

    def send_packet(self, data: bytes, encoder, needs_encode=True) -> None:
        """Send a packet to the voice socket"""
        self.sock_sequence += 1
        if self.sock_sequence > 0xFFFF:
            self.sock_sequence = 0

        if self.timestamp > 0xFFFFFFFF:
            self.timestamp = 0

        if needs_encode:
            data = encoder.encode(data)
        packet = self.generate_packet(data)

        self.socket.sendto(packet, (self.voice_ip, self.voice_port))
        self.timestamp += encoder.samples_per_frame

    async def send_heartbeat(self) -> None:
        await self.send_json({"op": OP.HEARTBEAT, "d": random.uniform(0.0, 1.0)})
        logger.debug("❤ Voice Connection is sending Heartbeat")

    async def _identify(self) -> None:
        """Send an identify payload to the voice gateway."""
        payload = {
            "op": OP.IDENTIFY,
            "d": {
                "server_id": self.guild_id,
                "user_id": self.state.client.user.id,
                "session_id": self.session_id,
                "token": self.token,
            },
        }
        serialized = OverriddenJson.dumps(payload)
        await self.ws.send_str(serialized)

        logger.debug("Voice Connection has identified itself to Voice Gateway")

    async def _select_protocol(self) -> None:
        """Inform Discord of our chosen protocol."""
        payload = {
            "op": OP.SELECT_PROTOCOL,
            "d": {
                "protocol": "udp",
                "data": {"address": self.me_ip, "port": self.me_port, "mode": self.voice_modes[0]},
            },
        }
        await self.send_json(payload)

    async def speaking(self, is_speaking: bool = True) -> None:
        """
        Tell the gateway if we're sending audio or not.

        Args:
            is_speaking: If we're sending audio or not

        """
        payload = {
            "op": OP.SPEAKING,
            "d": {
                "speaking": 1 << 0 if is_speaking else 0,
                "delay": 0,
                "ssrc": self.ssrc,
            },
        }
        await self.ws.send_json(payload)

    def set_new_voice_server(self, payload: dict) -> None:
        """
        Set a new voice server to connect to.

        Args:
            payload: New voice server connection data

        """
        self.ws_url = f"wss://{payload['endpoint']}?v=4"
        self.token = payload["token"]
        self.guild_id = payload["guild_id"]
        self._voice_server_update.set()

run() async

Start receiving events from the websocket.

Source code in naff/api/voice/voice_gateway.py
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
async def run(self) -> None:
    """Start receiving events from the websocket."""
    while True:
        if self._stopping is None:
            self._stopping = asyncio.create_task(self._close_gateway.wait())
        receiving = asyncio.create_task(self.receive())
        done, _ = await asyncio.wait({self._stopping, receiving}, return_when=asyncio.FIRST_COMPLETED)

        if receiving in done:
            # Note that we check for a received message first, because if both completed at
            # the same time, we don't want to discard that message.
            msg = await receiving
        else:
            # This has to be the stopping task, which we join into the current task (even
            # though that doesn't give any meaningful value in the return).
            await self._stopping
            receiving.cancel()
            return

        op = msg.get("op")
        data = msg.get("d")
        seq = msg.get("s")

        if seq:
            self.sequence = seq

        # This may try to reconnect the connection so it is best to wait
        # for it to complete before receiving more - that way there's less
        # possible race conditions to consider.
        await self.dispatch_opcode(data, op)

establish_voice_socket() async

Establish the socket connection to discord

Source code in naff/api/voice/voice_gateway.py
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
async def establish_voice_socket(self) -> None:
    """Establish the socket connection to discord"""
    logger.debug("IP Discovery in progress...")

    self.socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    self.socket.setblocking(False)

    packet = bytearray(70)
    struct.pack_into(">H", packet, 0, 1)  # 1 = Send
    struct.pack_into(">H", packet, 2, 70)  # 70 = Length
    struct.pack_into(">I", packet, 4, self.ssrc)

    self.socket.sendto(packet, (self.voice_ip, self.voice_port))
    resp = await self.loop.sock_recv(self.socket, 70)
    logger.debug(f"Voice Initial Response Received: {resp}")

    ip_start = 4
    ip_end = resp.index(0, ip_start)
    self.me_ip = resp[ip_start:ip_end].decode("ascii")

    self.me_port = struct.unpack_from(">H", resp, len(resp) - 2)[0]
    logger.debug(f"IP Discovered: {self.me_ip} #{self.me_port}")

    await self._select_protocol()

generate_packet(data)

Generate a packet to be sent to the voice socket.

Source code in naff/api/voice/voice_gateway.py
274
275
276
277
278
279
280
281
282
283
284
def generate_packet(self, data: bytes) -> bytes:
    """Generate a packet to be sent to the voice socket."""
    header = bytearray(12)
    header[0] = 0x80
    header[1] = 0x78

    struct.pack_into(">H", header, 2, self.sock_sequence)
    struct.pack_into(">I", header, 4, self.timestamp)
    struct.pack_into(">I", header, 8, self.ssrc)

    return self.encryptor.encrypt(self.voice_modes[0], header, data)

send_packet(data, encoder, needs_encode=True)

Send a packet to the voice socket

Source code in naff/api/voice/voice_gateway.py
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
def send_packet(self, data: bytes, encoder, needs_encode=True) -> None:
    """Send a packet to the voice socket"""
    self.sock_sequence += 1
    if self.sock_sequence > 0xFFFF:
        self.sock_sequence = 0

    if self.timestamp > 0xFFFFFFFF:
        self.timestamp = 0

    if needs_encode:
        data = encoder.encode(data)
    packet = self.generate_packet(data)

    self.socket.sendto(packet, (self.voice_ip, self.voice_port))
    self.timestamp += encoder.samples_per_frame

speaking(is_speaking=True) async

Tell the gateway if we're sending audio or not.

Parameters:

Name Type Description Default
is_speaking bool

If we're sending audio or not

True
Source code in naff/api/voice/voice_gateway.py
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
async def speaking(self, is_speaking: bool = True) -> None:
    """
    Tell the gateway if we're sending audio or not.

    Args:
        is_speaking: If we're sending audio or not

    """
    payload = {
        "op": OP.SPEAKING,
        "d": {
            "speaking": 1 << 0 if is_speaking else 0,
            "delay": 0,
            "ssrc": self.ssrc,
        },
    }
    await self.ws.send_json(payload)

set_new_voice_server(payload)

Set a new voice server to connect to.

Parameters:

Name Type Description Default
payload dict

New voice server connection data

required
Source code in naff/api/voice/voice_gateway.py
351
352
353
354
355
356
357
358
359
360
361
362
def set_new_voice_server(self, payload: dict) -> None:
    """
    Set a new voice server to connect to.

    Args:
        payload: New voice server connection data

    """
    self.ws_url = f"wss://{payload['endpoint']}?v=4"
    self.token = payload["token"]
    self.guild_id = payload["guild_id"]
    self._voice_server_update.set()

This file handles the interaction with discords http endpoints.

GlobalLock

Manages the global ratelimit

Source code in naff/api/http/http_client.py
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
class GlobalLock:
    """Manages the global ratelimit"""

    def __init__(self) -> None:
        self.cooldown_system: CooldownSystem = CooldownSystem(
            45, 1
        )  # global rate-limit is 50 per second, conservatively we use 45
        self._lock: asyncio.Lock = asyncio.Lock()

    async def rate_limit(self) -> None:
        async with self._lock:
            while not self.cooldown_system.acquire_token():
                await asyncio.sleep(self.cooldown_system.get_cooldown_time())

    async def lock(self, delta: float) -> None:
        """
        Lock the global lock for a given duration.

        Args:
            delta: The time to keep the lock acquired
        """
        await self._lock.acquire()
        await asyncio.sleep(delta)
        self._lock.release()

lock(delta) async

Lock the global lock for a given duration.

Parameters:

Name Type Description Default
delta float

The time to keep the lock acquired

required
Source code in naff/api/http/http_client.py
59
60
61
62
63
64
65
66
67
68
async def lock(self, delta: float) -> None:
    """
    Lock the global lock for a given duration.

    Args:
        delta: The time to keep the lock acquired
    """
    await self._lock.acquire()
    await asyncio.sleep(delta)
    self._lock.release()

BucketLock

Manages the ratelimit for each bucket

Source code in naff/api/http/http_client.py
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
class BucketLock:
    """Manages the ratelimit for each bucket"""

    def __init__(self) -> None:
        self._lock: asyncio.Lock = asyncio.Lock()

        self.unlock_on_exit: bool = True

        self.bucket_hash: str | None = None
        self.limit: int = -1
        self.remaining: int = -1
        self.delta: float = 0.0

    def __repr__(self) -> str:
        return f"<BucketLock: {self.bucket_hash or 'Generic'}>"

    @property
    def locked(self) -> bool:
        """Return True if lock is acquired."""
        return self._lock.locked()

    def unlock(self) -> None:
        """Unlock this bucket."""
        self._lock.release()

    def ingest_ratelimit_header(self, header: CIMultiDictProxy) -> None:
        """
        Ingests a discord rate limit header to configure this bucket lock.

        Args:
            header: A header from a http response
        """
        self.bucket_hash = header.get("x-ratelimit-bucket")
        self.limit = int(header.get("x-ratelimit-limit") or -1)
        self.remaining = int(header.get("x-ratelimit-remaining") or -1)
        self.delta = float(header.get("x-ratelimit-reset-after", 0.0))

    async def blind_defer_unlock(self) -> None:
        """Unlocks the BucketLock but doesn't wait for completion."""
        self.unlock_on_exit = False
        loop = asyncio.get_running_loop()
        loop.call_later(self.delta, self.unlock)

    async def defer_unlock(self, reset_after: float | None = None) -> None:
        """Unlocks the BucketLock after a specified delay."""
        self.unlock_on_exit = False
        await asyncio.sleep(reset_after or self.delta)
        self.unlock()

    async def __aenter__(self) -> None:
        await self._lock.acquire()

    async def __aexit__(self, *args) -> None:
        if self.unlock_on_exit and self._lock.locked():
            self.unlock()
        self.unlock_on_exit = True

locked() property

Return True if lock is acquired.

Source code in naff/api/http/http_client.py
87
88
89
90
@property
def locked(self) -> bool:
    """Return True if lock is acquired."""
    return self._lock.locked()

unlock()

Unlock this bucket.

Source code in naff/api/http/http_client.py
92
93
94
def unlock(self) -> None:
    """Unlock this bucket."""
    self._lock.release()

ingest_ratelimit_header(header)

Ingests a discord rate limit header to configure this bucket lock.

Parameters:

Name Type Description Default
header CIMultiDictProxy

A header from a http response

required
Source code in naff/api/http/http_client.py
 96
 97
 98
 99
100
101
102
103
104
105
106
def ingest_ratelimit_header(self, header: CIMultiDictProxy) -> None:
    """
    Ingests a discord rate limit header to configure this bucket lock.

    Args:
        header: A header from a http response
    """
    self.bucket_hash = header.get("x-ratelimit-bucket")
    self.limit = int(header.get("x-ratelimit-limit") or -1)
    self.remaining = int(header.get("x-ratelimit-remaining") or -1)
    self.delta = float(header.get("x-ratelimit-reset-after", 0.0))

blind_defer_unlock() async

Unlocks the BucketLock but doesn't wait for completion.

Source code in naff/api/http/http_client.py
108
109
110
111
112
async def blind_defer_unlock(self) -> None:
    """Unlocks the BucketLock but doesn't wait for completion."""
    self.unlock_on_exit = False
    loop = asyncio.get_running_loop()
    loop.call_later(self.delta, self.unlock)

defer_unlock(reset_after=None) async

Unlocks the BucketLock after a specified delay.

Source code in naff/api/http/http_client.py
114
115
116
117
118
async def defer_unlock(self, reset_after: float | None = None) -> None:
    """Unlocks the BucketLock after a specified delay."""
    self.unlock_on_exit = False
    await asyncio.sleep(reset_after or self.delta)
    self.unlock()

HTTPClient

Bases: BotRequests, ChannelRequests, EmojiRequests, GuildRequests, InteractionRequests, MemberRequests, MessageRequests, ReactionRequests, StickerRequests, ThreadRequests, UserRequests, WebhookRequests, ScheduledEventsRequests

A http client for sending requests to the Discord API.

Source code in naff/api/http/http_client.py
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
class HTTPClient(
    BotRequests,
    ChannelRequests,
    EmojiRequests,
    GuildRequests,
    InteractionRequests,
    MemberRequests,
    MessageRequests,
    ReactionRequests,
    StickerRequests,
    ThreadRequests,
    UserRequests,
    WebhookRequests,
    ScheduledEventsRequests,
):
    """A http client for sending requests to the Discord API."""

    def __init__(self, connector: BaseConnector | None = None) -> None:
        self.connector: BaseConnector | None = connector
        self.__session: ClientSession | None = None
        self.token: str | None = None
        self.global_lock: GlobalLock = GlobalLock()
        self._max_attempts: int = 3

        self.ratelimit_locks: WeakValueDictionary[str, BucketLock] = WeakValueDictionary()
        self._endpoints = {}

        self.user_agent: str = (
            f"DiscordBot ({__repo_url__} {__version__} Python/{__py_version__}) aiohttp/{aiohttp.__version__}"
        )

    def get_ratelimit(self, route: Route) -> BucketLock:
        """
        Get a route's rate limit bucket.

        Args:
            route: The route to fetch the ratelimit bucket for

        Returns:
            The BucketLock object for this route
        """
        if bucket_hash := self._endpoints.get(route.rl_bucket):
            # we have seen this route before, we know which bucket it is associated with
            lock = self.ratelimit_locks.get(bucket_hash)
            if lock:
                # if we have an active lock on this route, it'll still be in the cache
                # return that lock
                return lock
        # if no cached lock exists, return a new lock
        return BucketLock()

    def ingest_ratelimit(self, route: Route, header: CIMultiDictProxy, bucket_lock: BucketLock) -> None:
        """
        Ingests a ratelimit header from discord to determine ratelimit.

        Args:
            route: The route we're ingesting ratelimit for
            header: The rate limit header in question
            bucket_lock: The rate limit bucket for this route
        """
        bucket_lock.ingest_ratelimit_header(header)

        if bucket_lock.bucket_hash:
            # We only ever try and cache the bucket if the bucket hash has been set (ignores unlimited endpoints)
            logger.debug(f"Caching ingested rate limit data for: {bucket_lock.bucket_hash}")
            self._endpoints[route.rl_bucket] = bucket_lock.bucket_hash
            self.ratelimit_locks[bucket_lock.bucket_hash] = bucket_lock

    @staticmethod
    def _process_payload(
        payload: dict | list[dict] | None, files: UPLOADABLE_TYPE | list[UPLOADABLE_TYPE] | None
    ) -> dict | list[dict] | FormData | None:
        """
        Processes a payload into a format safe for discord. Converts the payload into FormData where required

        Args:
            payload: The payload of the request
            files: A list of any files to send

        Returns:
            Either a dictionary or multipart data form
        """
        if payload is None:
            return None

        if isinstance(payload, dict):
            payload = dict_filter(payload)
        else:
            payload = [dict_filter(x) if isinstance(x, dict) else x for x in payload]

        if not files:
            return payload

        if not isinstance(files, list):
            files = [files]

        form_data = FormData()
        form_data.add_field("payload_json", OverriddenJson.dumps(payload))

        for index, file in enumerate(files):
            file_buffer = models.open_file(file)
            if isinstance(file, models.File):
                form_data.add_field(f"files[{index}]", file_buffer, filename=file.file_name)
            else:
                form_data.add_field(f"files[{index}]", file_buffer)
        return form_data

    async def request(
        self,
        route: Route,
        payload: list | dict | None = None,
        files: list[UPLOADABLE_TYPE] | None = None,
        reason: str | None = None,
        params: dict | None = None,
        **kwargs: dict,
    ) -> str | dict[str, Any] | None:
        """
        Make a request to discord.

        Args:
            route: The route to take
            payload: The payload for this request
            files: The files to send with this request
            reason: Attach a reason to this request, used for audit logs

        """
        # Assemble headers
        kwargs["headers"] = {"User-Agent": self.user_agent}
        if self.token:
            kwargs["headers"]["Authorization"] = f"Bot {self.token}"
        if reason:
            kwargs["headers"]["X-Audit-Log-Reason"] = _uriquote(reason, safe="/ ")

        if isinstance(payload, (list, dict)) and not files:
            kwargs["headers"]["Content-Type"] = "application/json"
        if isinstance(params, dict):
            kwargs["params"] = dict_filter(params)

        lock = self.get_ratelimit(route)
        # this gets a BucketLock for this route.
        # If this endpoint has been used before, it will get an existing ratelimit for the respective buckethash
        # otherwise a brand-new bucket lock will be returned

        for attempt in range(self._max_attempts):
            async with lock:
                try:
                    await self.global_lock.rate_limit()
                    # prevent us exceeding the global rate limit by throttling http requests

                    if cast(ClientSession, self.__session).closed:
                        await self.login(cast(str, self.token))

                    processed_data = self._process_payload(payload, files)
                    if isinstance(processed_data, FormData):
                        kwargs["data"] = processed_data  # pyright: ignore
                    else:
                        kwargs["json"] = processed_data  # pyright: ignore

                    async with cast(ClientSession, self.__session).request(
                        route.method, route.url, **kwargs
                    ) as response:
                        result = await response_decode(response)
                        self.ingest_ratelimit(route, response.headers, lock)

                        if response.status == 429:
                            # ratelimit exceeded
                            result = cast(dict[str, str], result)
                            if result.get("global", False):
                                # global ratelimit is reached
                                # if we get a global, that's pretty bad, this would usually happen if the user is hitting the api from 2 clients sharing a token
                                logger.error(
                                    f"Bot has exceeded global ratelimit, locking REST API for {result['retry_after']} seconds"
                                )
                                await self.global_lock.lock(float(result["retry_after"]))
                                continue
                            elif result.get("message") == "The resource is being rate limited.":
                                # resource ratelimit is reached
                                logger.warning(
                                    f"{route.endpoint} The resource is being rate limited! "
                                    f"Reset in {result.get('retry_after')} seconds"
                                )
                                # lock this resource and wait for unlock
                                await lock.defer_unlock(float(result["retry_after"]))
                                continue
                            else:
                                # endpoint ratelimit is reached
                                # 429's are unfortunately unavoidable, but we can attempt to avoid them
                                # so long as these are infrequent we're doing well
                                logger.warning(
                                    f"{route.endpoint} Has exceeded it's ratelimit ({lock.limit})! Reset in {lock.delta} seconds"
                                )
                                await lock.defer_unlock()  # lock this route and wait for unlock
                                continue
                        elif lock.remaining == 0:
                            # Last call available in the bucket, lock until reset
                            logger.debug(
                                f"{route.endpoint} Has exhausted its ratelimit ({lock.limit})! Locking route for {lock.delta} seconds"
                            )
                            await lock.blind_defer_unlock()  # lock this route, but continue processing the current response

                        elif response.status in {500, 502, 504}:
                            # Server issues, retry
                            logger.warning(
                                f"{route.endpoint} Received {response.status}... retrying in {1 + attempt * 2} seconds"
                            )
                            await asyncio.sleep(1 + attempt * 2)
                            continue

                        if not 300 > response.status >= 200:
                            await self._raise_exception(response, route, result)

                        logger.debug(
                            f"{route.endpoint} Received {response.status} :: [{lock.remaining}/{lock.limit} calls remaining]"
                        )
                        return result
                except OSError as e:
                    if attempt < self._max_attempts - 1 and e.errno in (54, 10054):
                        await asyncio.sleep(1 + attempt * 2)
                        continue
                    raise

    async def _raise_exception(self, response, route, result) -> None:
        logger.error(f"{route.method}::{route.url}: {response.status}")

        if response.status == 403:
            raise Forbidden(response, response_data=result, route=route)
        elif response.status == 404:
            raise NotFound(response, response_data=result, route=route)
        elif response.status >= 500:
            raise DiscordError(response, response_data=result, route=route)
        else:
            raise HTTPException(response, response_data=result, route=route)

    async def request_cdn(self, url, asset) -> bytes:  # pyright: ignore [reportGeneralTypeIssues]
        logger.debug(f"{asset} requests {url} from CDN")
        async with cast(ClientSession, self.__session).get(url) as response:
            if response.status == 200:
                return await response.read()
            await self._raise_exception(response, asset, await response_decode(response))

    async def login(self, token: str) -> dict[str, Any]:
        """
        "Login" to the gateway, basically validates the token and grabs user data.

        Args:
            token: the token to use

        Returns:
            The currently logged in bot's data

        """
        self.__session = ClientSession(connector=self.connector)
        self.token = token
        try:
            result = await self.request(Route("GET", "/users/@me"))
            return cast(dict[str, Any], result)
        except HTTPException as e:
            if e.status == 401:
                raise LoginError("An improper token was passed") from e
            raise

    async def close(self) -> None:
        """Close the session."""
        if self.__session and not self.__session.closed:
            await self.__session.close()

    async def get_gateway(self) -> str:
        """
        Gets the gateway url.

        Returns:
            The gateway url

        """
        try:
            result = await self.request(Route("GET", "/gateway"))
            result = cast(dict[str, Any], result)
        except HTTPException as exc:
            raise GatewayNotFound from exc
        return "{0}?encoding={1}&v={2}&compress=zlib-stream".format(result["url"], "json", __api_version__)

    async def get_gateway_bot(self) -> discord_typings.GetGatewayBotData:
        try:
            result = await self.request(Route("GET", "/gateway/bot"))
        except HTTPException as exc:
            raise GatewayNotFound from exc
        return cast(discord_typings.GetGatewayBotData, result)

    async def websocket_connect(self, url: str) -> ClientWebSocketResponse:
        """
        Connect to the websocket.

        Args:
            url: the url to connect to

        """
        return await cast(ClientSession, self.__session).ws_connect(
            url, timeout=30, max_msg_size=0, autoclose=False, headers={"User-Agent": self.user_agent}, compress=0
        )

get_ratelimit(route)

Get a route's rate limit bucket.

Parameters:

Name Type Description Default
route Route

The route to fetch the ratelimit bucket for

required

Returns:

Type Description
BucketLock

The BucketLock object for this route

Source code in naff/api/http/http_client.py
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
def get_ratelimit(self, route: Route) -> BucketLock:
    """
    Get a route's rate limit bucket.

    Args:
        route: The route to fetch the ratelimit bucket for

    Returns:
        The BucketLock object for this route
    """
    if bucket_hash := self._endpoints.get(route.rl_bucket):
        # we have seen this route before, we know which bucket it is associated with
        lock = self.ratelimit_locks.get(bucket_hash)
        if lock:
            # if we have an active lock on this route, it'll still be in the cache
            # return that lock
            return lock
    # if no cached lock exists, return a new lock
    return BucketLock()

ingest_ratelimit(route, header, bucket_lock)

Ingests a ratelimit header from discord to determine ratelimit.

Parameters:

Name Type Description Default
route Route

The route we're ingesting ratelimit for

required
header CIMultiDictProxy

The rate limit header in question

required
bucket_lock BucketLock

The rate limit bucket for this route

required
Source code in naff/api/http/http_client.py
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
def ingest_ratelimit(self, route: Route, header: CIMultiDictProxy, bucket_lock: BucketLock) -> None:
    """
    Ingests a ratelimit header from discord to determine ratelimit.

    Args:
        route: The route we're ingesting ratelimit for
        header: The rate limit header in question
        bucket_lock: The rate limit bucket for this route
    """
    bucket_lock.ingest_ratelimit_header(header)

    if bucket_lock.bucket_hash:
        # We only ever try and cache the bucket if the bucket hash has been set (ignores unlimited endpoints)
        logger.debug(f"Caching ingested rate limit data for: {bucket_lock.bucket_hash}")
        self._endpoints[route.rl_bucket] = bucket_lock.bucket_hash
        self.ratelimit_locks[bucket_lock.bucket_hash] = bucket_lock

request(route, payload=None, files=None, reason=None, params=None, **kwargs) async

Make a request to discord.

Parameters:

Name Type Description Default
route Route

The route to take

required
payload list | dict | None

The payload for this request

None
files list[UPLOADABLE_TYPE] | None

The files to send with this request

None
reason str | None

Attach a reason to this request, used for audit logs

None
Source code in naff/api/http/http_client.py
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
async def request(
    self,
    route: Route,
    payload: list | dict | None = None,
    files: list[UPLOADABLE_TYPE] | None = None,
    reason: str | None = None,
    params: dict | None = None,
    **kwargs: dict,
) -> str | dict[str, Any] | None:
    """
    Make a request to discord.

    Args:
        route: The route to take
        payload: The payload for this request
        files: The files to send with this request
        reason: Attach a reason to this request, used for audit logs

    """
    # Assemble headers
    kwargs["headers"] = {"User-Agent": self.user_agent}
    if self.token:
        kwargs["headers"]["Authorization"] = f"Bot {self.token}"
    if reason:
        kwargs["headers"]["X-Audit-Log-Reason"] = _uriquote(reason, safe="/ ")

    if isinstance(payload, (list, dict)) and not files:
        kwargs["headers"]["Content-Type"] = "application/json"
    if isinstance(params, dict):
        kwargs["params"] = dict_filter(params)

    lock = self.get_ratelimit(route)
    # this gets a BucketLock for this route.
    # If this endpoint has been used before, it will get an existing ratelimit for the respective buckethash
    # otherwise a brand-new bucket lock will be returned

    for attempt in range(self._max_attempts):
        async with lock:
            try:
                await self.global_lock.rate_limit()
                # prevent us exceeding the global rate limit by throttling http requests

                if cast(ClientSession, self.__session).closed:
                    await self.login(cast(str, self.token))

                processed_data = self._process_payload(payload, files)
                if isinstance(processed_data, FormData):
                    kwargs["data"] = processed_data  # pyright: ignore
                else:
                    kwargs["json"] = processed_data  # pyright: ignore

                async with cast(ClientSession, self.__session).request(
                    route.method, route.url, **kwargs
                ) as response:
                    result = await response_decode(response)
                    self.ingest_ratelimit(route, response.headers, lock)

                    if response.status == 429:
                        # ratelimit exceeded
                        result = cast(dict[str, str], result)
                        if result.get("global", False):
                            # global ratelimit is reached
                            # if we get a global, that's pretty bad, this would usually happen if the user is hitting the api from 2 clients sharing a token
                            logger.error(
                                f"Bot has exceeded global ratelimit, locking REST API for {result['retry_after']} seconds"
                            )
                            await self.global_lock.lock(float(result["retry_after"]))
                            continue
                        elif result.get("message") == "The resource is being rate limited.":
                            # resource ratelimit is reached
                            logger.warning(
                                f"{route.endpoint} The resource is being rate limited! "
                                f"Reset in {result.get('retry_after')} seconds"
                            )
                            # lock this resource and wait for unlock
                            await lock.defer_unlock(float(result["retry_after"]))
                            continue
                        else:
                            # endpoint ratelimit is reached
                            # 429's are unfortunately unavoidable, but we can attempt to avoid them
                            # so long as these are infrequent we're doing well
                            logger.warning(
                                f"{route.endpoint} Has exceeded it's ratelimit ({lock.limit})! Reset in {lock.delta} seconds"
                            )
                            await lock.defer_unlock()  # lock this route and wait for unlock
                            continue
                    elif lock.remaining == 0:
                        # Last call available in the bucket, lock until reset
                        logger.debug(
                            f"{route.endpoint} Has exhausted its ratelimit ({lock.limit})! Locking route for {lock.delta} seconds"
                        )
                        await lock.blind_defer_unlock()  # lock this route, but continue processing the current response

                    elif response.status in {500, 502, 504}:
                        # Server issues, retry
                        logger.warning(
                            f"{route.endpoint} Received {response.status}... retrying in {1 + attempt * 2} seconds"
                        )
                        await asyncio.sleep(1 + attempt * 2)
                        continue

                    if not 300 > response.status >= 200:
                        await self._raise_exception(response, route, result)

                    logger.debug(
                        f"{route.endpoint} Received {response.status} :: [{lock.remaining}/{lock.limit} calls remaining]"
                    )
                    return result
            except OSError as e:
                if attempt < self._max_attempts - 1 and e.errno in (54, 10054):
                    await asyncio.sleep(1 + attempt * 2)
                    continue
                raise

login(token) async

"Login" to the gateway, basically validates the token and grabs user data.

Parameters:

Name Type Description Default
token str

the token to use

required

Returns:

Type Description
dict[str, Any]

The currently logged in bot's data

Source code in naff/api/http/http_client.py
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
async def login(self, token: str) -> dict[str, Any]:
    """
    "Login" to the gateway, basically validates the token and grabs user data.

    Args:
        token: the token to use

    Returns:
        The currently logged in bot's data

    """
    self.__session = ClientSession(connector=self.connector)
    self.token = token
    try:
        result = await self.request(Route("GET", "/users/@me"))
        return cast(dict[str, Any], result)
    except HTTPException as e:
        if e.status == 401:
            raise LoginError("An improper token was passed") from e
        raise

close() async

Close the session.

Source code in naff/api/http/http_client.py
390
391
392
393
async def close(self) -> None:
    """Close the session."""
    if self.__session and not self.__session.closed:
        await self.__session.close()

get_gateway() async

Gets the gateway url.

Returns:

Type Description
str

The gateway url

Source code in naff/api/http/http_client.py
395
396
397
398
399
400
401
402
403
404
405
406
407
408
async def get_gateway(self) -> str:
    """
    Gets the gateway url.

    Returns:
        The gateway url

    """
    try:
        result = await self.request(Route("GET", "/gateway"))
        result = cast(dict[str, Any], result)
    except HTTPException as exc:
        raise GatewayNotFound from exc
    return "{0}?encoding={1}&v={2}&compress=zlib-stream".format(result["url"], "json", __api_version__)

websocket_connect(url) async

Connect to the websocket.

Parameters:

Name Type Description Default
url str

the url to connect to

required
Source code in naff/api/http/http_client.py
417
418
419
420
421
422
423
424
425
426
427
async def websocket_connect(self, url: str) -> ClientWebSocketResponse:
    """
    Connect to the websocket.

    Args:
        url: the url to connect to

    """
    return await cast(ClientSession, self.__session).ws_connect(
        url, timeout=30, max_msg_size=0, autoclose=False, headers={"User-Agent": self.user_agent}, compress=0
    )

Voice

AudioBuffer

Source code in naff/api/voice/audio.py
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
class AudioBuffer:
    def __init__(self) -> None:
        self._buffer = bytearray()
        self._lock = threading.Lock()
        self.initialised = threading.Event()

    def __len__(self) -> int:
        return len(self._buffer)

    def extend(self, data: bytes) -> None:
        """
        Extend the buffer with additional data.

        Args:
            data: The data to add
        """
        with self._lock:
            self._buffer.extend(data)

    def read(self, total_bytes: int) -> bytearray:
        """
        Read `total_bytes` bytes of audio from the buffer.

        Args:
            total_bytes: Amount of bytes to read.

        Returns:
            Desired amount of bytes
        """
        with self._lock:
            view = memoryview(self._buffer)
            self._buffer = bytearray(view[total_bytes:])
            data = bytearray(view[:total_bytes])
            if 0 < len(data) < total_bytes:
                # pad incomplete frames with 0's
                data.extend(b"\0" * (total_bytes - len(data)))
            return data

extend(data)

Extend the buffer with additional data.

Parameters:

Name Type Description Default
data bytes

The data to add

required
Source code in naff/api/voice/audio.py
26
27
28
29
30
31
32
33
34
def extend(self, data: bytes) -> None:
    """
    Extend the buffer with additional data.

    Args:
        data: The data to add
    """
    with self._lock:
        self._buffer.extend(data)

read(total_bytes)

Read total_bytes bytes of audio from the buffer.

Parameters:

Name Type Description Default
total_bytes int

Amount of bytes to read.

required

Returns:

Type Description
bytearray

Desired amount of bytes

Source code in naff/api/voice/audio.py
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
def read(self, total_bytes: int) -> bytearray:
    """
    Read `total_bytes` bytes of audio from the buffer.

    Args:
        total_bytes: Amount of bytes to read.

    Returns:
        Desired amount of bytes
    """
    with self._lock:
        view = memoryview(self._buffer)
        self._buffer = bytearray(view[total_bytes:])
        data = bytearray(view[:total_bytes])
        if 0 < len(data) < total_bytes:
            # pad incomplete frames with 0's
            data.extend(b"\0" * (total_bytes - len(data)))
        return data

BaseAudio

Bases: ABC

Base structure of the audio.

Source code in naff/api/voice/audio.py
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
class BaseAudio(ABC):
    """Base structure of the audio."""

    locked_stream: bool
    """Prevents the audio task from closing automatically when no data is received."""
    needs_encode: bool
    """Does this audio data need encoding with opus?"""
    bitrate: Optional[int]
    """Optionally specify a specific bitrate to encode this audio data with"""

    def __del__(self) -> None:
        self.cleanup()

    def cleanup(self) -> None:
        """A method to optionally cleanup after this object is no longer required."""
        ...

    @property
    def audio_complete(self) -> bool:
        """A property to tell the player if more audio is expected."""
        return False

    @abstractmethod
    def read(self, frame_size: int) -> bytes:
        """
        Reads frame_size ms of audio from source.

        returns:
            bytes of audio
        """
        ...

locked_stream: bool class-attribute

Prevents the audio task from closing automatically when no data is received.

needs_encode: bool class-attribute

Does this audio data need encoding with opus?

bitrate: Optional[int] class-attribute

Optionally specify a specific bitrate to encode this audio data with

cleanup()

A method to optionally cleanup after this object is no longer required.

Source code in naff/api/voice/audio.py
69
70
71
def cleanup(self) -> None:
    """A method to optionally cleanup after this object is no longer required."""
    ...

audio_complete() property

A property to tell the player if more audio is expected.

Source code in naff/api/voice/audio.py
73
74
75
76
@property
def audio_complete(self) -> bool:
    """A property to tell the player if more audio is expected."""
    return False

read(frame_size) abstractmethod

Reads frame_size ms of audio from source.

Returns:

Type Description
bytes

bytes of audio

Source code in naff/api/voice/audio.py
78
79
80
81
82
83
84
85
86
@abstractmethod
def read(self, frame_size: int) -> bytes:
    """
    Reads frame_size ms of audio from source.

    returns:
        bytes of audio
    """
    ...

Audio

Bases: BaseAudio

Audio for playing from file or URL.

Source code in naff/api/voice/audio.py
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
class Audio(BaseAudio):
    """Audio for playing from file or URL."""

    source: str
    """The source ffmpeg should use to play the audio"""
    process: subprocess.Popen
    """The ffmpeg process to use"""
    buffer: AudioBuffer
    """The audio objects buffer to prevent stuttering"""
    buffer_seconds: int
    """How many seconds of audio should be buffered"""
    read_ahead_task: threading.Thread
    """A thread that reads ahead to create the buffer"""
    ffmpeg_args: str | list[str]
    """Args to pass to ffmpeg"""
    ffmpeg_before_args: str | list[str]
    """Args to pass to ffmpeg before the source"""

    def __init__(self, src: Union[str, Path]) -> None:
        self.source = src
        self.needs_encode = True
        self.locked_stream = False
        self.process: Optional[subprocess.Popen] = None

        self.buffer = AudioBuffer()

        self.buffer_seconds = 3
        self.read_ahead_task = threading.Thread(target=self._read_ahead, daemon=True)

        self.ffmpeg_before_args = ""
        self.ffmpeg_args = ""

    def __repr__(self) -> str:
        return f"<{type(self).__name__}: {self.source}>"

    @property
    def _max_buffer_size(self) -> int:
        # 1ms of audio * (buffer seconds * 1000)
        return 192 * (self.buffer_seconds * 1000)

    @property
    def audio_complete(self) -> bool:
        """Uses the state of the subprocess to determine if more audio is coming"""
        if self.process:
            if self.process.poll() is None:
                return False
        return True

    def _create_process(self, *, block: bool = True) -> None:
        before = (
            self.ffmpeg_before_args if isinstance(self.ffmpeg_before_args, list) else self.ffmpeg_before_args.split()
        )
        after = self.ffmpeg_args if isinstance(self.ffmpeg_args, list) else self.ffmpeg_args.split()
        cmd = [
            "ffmpeg",
            "-i",
            self.source,
            "-f",
            "s16le",
            "-ar",
            "48000",
            "-ac",
            "2",
            "-loglevel",
            "warning",
            "pipe:1",
            "-vn",
        ]
        cmd[1:1] = before
        cmd.extend(after)

        self.process = subprocess.Popen(  # noqa: S603
            cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.DEVNULL
        )
        self.read_ahead_task.start()

        if block:
            # block until some data is in the buffer
            self.buffer.initialised.wait()

    def _read_ahead(self) -> None:
        while self.process:
            if self.process.poll() is not None:
                # ffmpeg has exited, stop reading ahead
                if not self.buffer.initialised.is_set():
                    # assume this is a small file and initialise the buffer
                    self.buffer.initialised.set()

                return
            if not len(self.buffer) >= self._max_buffer_size:
                self.buffer.extend(self.process.stdout.read(3840))
            else:
                if not self.buffer.initialised.is_set():
                    self.buffer.initialised.set()
                time.sleep(0.1)

    def pre_buffer(self, duration: None | float = None) -> None:
        """
        Start pre-buffering the audio.

        Args:
            duration: The duration of audio to pre-buffer.
        """
        if duration:
            self.buffer_seconds = duration

        if self.process and self.process.poll() is None:
            raise RuntimeError("Cannot pre-buffer an already running process")
        # sanity value enforcement to prevent audio weirdness
        self.buffer = AudioBuffer()
        self.buffer.initialised.clear()

        self._create_process(block=False)

    def read(self, frame_size: int) -> bytes:
        """
        Reads frame_size bytes of audio from the buffer.

        returns:
            bytes of audio
        """
        if not self.process:
            self._create_process()
        if not self.buffer.initialised.is_set():
            # we cannot start playing until the buffer is initialised
            self.buffer.initialised.wait()

        data = self.buffer.read(frame_size)

        if len(data) != frame_size:
            data = b""

        return bytes(data)

    def cleanup(self) -> None:
        """Cleans up after this audio object."""
        if self.process and self.process.poll() is None:
            self.process.kill()
            self.process.wait()

audio_complete() property

Uses the state of the subprocess to determine if more audio is coming

Source code in naff/api/voice/audio.py
129
130
131
132
133
134
135
@property
def audio_complete(self) -> bool:
    """Uses the state of the subprocess to determine if more audio is coming"""
    if self.process:
        if self.process.poll() is None:
            return False
    return True

pre_buffer(duration=None)

Start pre-buffering the audio.

Parameters:

Name Type Description Default
duration None | float

The duration of audio to pre-buffer.

None
Source code in naff/api/voice/audio.py
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
def pre_buffer(self, duration: None | float = None) -> None:
    """
    Start pre-buffering the audio.

    Args:
        duration: The duration of audio to pre-buffer.
    """
    if duration:
        self.buffer_seconds = duration

    if self.process and self.process.poll() is None:
        raise RuntimeError("Cannot pre-buffer an already running process")
    # sanity value enforcement to prevent audio weirdness
    self.buffer = AudioBuffer()
    self.buffer.initialised.clear()

    self._create_process(block=False)

read(frame_size)

Reads frame_size bytes of audio from the buffer.

Returns:

Type Description
bytes

bytes of audio

Source code in naff/api/voice/audio.py
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
def read(self, frame_size: int) -> bytes:
    """
    Reads frame_size bytes of audio from the buffer.

    returns:
        bytes of audio
    """
    if not self.process:
        self._create_process()
    if not self.buffer.initialised.is_set():
        # we cannot start playing until the buffer is initialised
        self.buffer.initialised.wait()

    data = self.buffer.read(frame_size)

    if len(data) != frame_size:
        data = b""

    return bytes(data)

cleanup()

Cleans up after this audio object.

Source code in naff/api/voice/audio.py
223
224
225
226
227
def cleanup(self) -> None:
    """Cleans up after this audio object."""
    if self.process and self.process.poll() is None:
        self.process.kill()
        self.process.wait()

AudioVolume

Bases: Audio

An audio object with volume control

Source code in naff/api/voice/audio.py
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
class AudioVolume(Audio):
    """An audio object with volume control"""

    _volume: float
    """The internal volume level of the audio"""

    def __init__(self, src: Union[str, Path]) -> None:
        super().__init__(src)
        self._volume = 0.5

    @property
    def volume(self) -> float:
        """The volume of the audio"""
        return self._volume

    @volume.setter
    def volume(self, value: float) -> None:
        """Sets the volume of the audio. Volume cannot be negative."""
        self._volume = max(value, 0.0)

    def read(self, frame_size: int) -> bytes:
        """
        Reads frame_size ms of audio from source.

        returns:
            bytes of audio
        """
        data = super().read(frame_size)
        return audioop.mul(data, 2, self._volume)

volume() property writable

The volume of the audio

Source code in naff/api/voice/audio.py
240
241
242
243
@property
def volume(self) -> float:
    """The volume of the audio"""
    return self._volume

read(frame_size)

Reads frame_size ms of audio from source.

Returns:

Type Description
bytes

bytes of audio

Source code in naff/api/voice/audio.py
250
251
252
253
254
255
256
257
258
def read(self, frame_size: int) -> bytes:
    """
    Reads frame_size ms of audio from source.

    returns:
        bytes of audio
    """
    data = super().read(frame_size)
    return audioop.mul(data, 2, self._volume)

Player

Bases: threading.Thread

Source code in naff/api/voice/player.py
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
class Player(threading.Thread):
    def __init__(self, audio, v_state, loop) -> None:
        super().__init__()
        self.daemon = True

        self.current_audio: Optional[BaseAudio] = audio
        self.state: "ActiveVoiceState" = v_state
        self.loop: AbstractEventLoop = loop

        self._encoder: Encoder = Encoder()

        self._resume: threading.Event = threading.Event()

        self._stop_event: threading.Event = threading.Event()
        self._stopped: asyncio.Event = asyncio.Event()

        self._sent_payloads: int = 0

        self._cond = threading.Condition()

        if not shutil.which("ffmpeg"):
            raise RuntimeError(
                "Unable to start player. FFmpeg was not found. Please add it to your project directory or PATH. (https://ffmpeg.org/)"
            )

    def __enter__(self) -> "Player":
        self.state.ws.cond = self._cond
        return self

    def __exit__(self, exc_type, exc_val, exc_tb) -> None:
        try:
            self.state.ws.cond = None
        except AttributeError:
            pass

    def stop(self) -> None:
        """Stop playing completely."""
        self._stop_event.set()
        with self._cond:
            self._cond.notify()

    def resume(self) -> None:
        """Resume playing."""
        self._resume.set()
        with self._cond:
            self._cond.notify()

    @property
    def paused(self) -> bool:
        """Is the player paused"""
        return not self._resume.is_set()

    def pause(self) -> None:
        """Pause the player."""
        self._resume.clear()

    @property
    def stopped(self) -> bool:
        """Is the player currently stopped?"""
        return self._stopped.is_set()

    @property
    def elapsed_time(self) -> float:
        """How many seconds of audio the player has sent."""
        return self._sent_payloads * self._encoder.delay

    def play(self) -> None:
        """Start playing."""
        self._stop_event.clear()
        self._resume.set()
        self.start()

    def run(self) -> None:
        """The main player loop to send audio to the voice websocket."""
        loops = 0

        if isinstance(self.current_audio, AudioVolume):
            # noinspection PyProtectedMember
            self.current_audio.volume = self.state._volume

        self._encoder.set_bitrate(getattr(self.current_audio, "bitrate", self.state.channel.bitrate))

        self._stopped.clear()

        asyncio.run_coroutine_threadsafe(self.state.ws.speaking(True), self.loop)
        logger.debug(f"Now playing {self.current_audio!r}")
        start = None

        try:
            while not self._stop_event.is_set():
                if not self.state.ws.ready.is_set() or not self._resume.is_set():
                    run_coroutine_threadsafe(self.state.ws.speaking(False), self.loop)
                    logger.debug("Voice playback has been suspended!")

                    wait_for = []

                    if not self.state.ws.ready.is_set():
                        wait_for.append(self.state.ws.ready)
                    if not self._resume.is_set():
                        wait_for.append(self._resume)

                    with self._cond:
                        while not (self._stop_event.is_set() or all(x.is_set() for x in wait_for)):
                            self._cond.wait()
                    if self._stop_event.is_set():
                        continue

                    run_coroutine_threadsafe(self.state.ws.speaking(), self.loop)
                    logger.debug("Voice playback has been resumed!")
                    start = None
                    loops = 0

                if data := self.current_audio.read(self._encoder.frame_size):
                    self.state.ws.send_packet(data, self._encoder, needs_encode=self.current_audio.needs_encode)
                else:
                    if self.current_audio.locked_stream or not self.current_audio.audio_complete:
                        # if more audio is expected
                        self.state.ws.send_packet(b"\xF8\xFF\xFE", self._encoder, needs_encode=False)
                    else:
                        break

                if not start:
                    start = perf_counter()

                loops += 1
                self._sent_payloads += 1  # used for duration calc
                sleep(max(0.0, start + (self._encoder.delay * loops) - perf_counter()))
        finally:
            asyncio.run_coroutine_threadsafe(self.state.ws.speaking(False), self.loop)
            self.current_audio.cleanup()
            self.loop.call_soon_threadsafe(self._stopped.set)

stop()

Stop playing completely.

Source code in naff/api/voice/player.py
52
53
54
55
56
def stop(self) -> None:
    """Stop playing completely."""
    self._stop_event.set()
    with self._cond:
        self._cond.notify()

resume()

Resume playing.

Source code in naff/api/voice/player.py
58
59
60
61
62
def resume(self) -> None:
    """Resume playing."""
    self._resume.set()
    with self._cond:
        self._cond.notify()

paused() property

Is the player paused

Source code in naff/api/voice/player.py
64
65
66
67
@property
def paused(self) -> bool:
    """Is the player paused"""
    return not self._resume.is_set()

pause()

Pause the player.

Source code in naff/api/voice/player.py
69
70
71
def pause(self) -> None:
    """Pause the player."""
    self._resume.clear()

stopped() property

Is the player currently stopped?

Source code in naff/api/voice/player.py
73
74
75
76
@property
def stopped(self) -> bool:
    """Is the player currently stopped?"""
    return self._stopped.is_set()

elapsed_time() property

How many seconds of audio the player has sent.

Source code in naff/api/voice/player.py
78
79
80
81
@property
def elapsed_time(self) -> float:
    """How many seconds of audio the player has sent."""
    return self._sent_payloads * self._encoder.delay

play()

Start playing.

Source code in naff/api/voice/player.py
83
84
85
86
87
def play(self) -> None:
    """Start playing."""
    self._stop_event.clear()
    self._resume.set()
    self.start()

run()

The main player loop to send audio to the voice websocket.

Source code in naff/api/voice/player.py
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
def run(self) -> None:
    """The main player loop to send audio to the voice websocket."""
    loops = 0

    if isinstance(self.current_audio, AudioVolume):
        # noinspection PyProtectedMember
        self.current_audio.volume = self.state._volume

    self._encoder.set_bitrate(getattr(self.current_audio, "bitrate", self.state.channel.bitrate))

    self._stopped.clear()

    asyncio.run_coroutine_threadsafe(self.state.ws.speaking(True), self.loop)
    logger.debug(f"Now playing {self.current_audio!r}")
    start = None

    try:
        while not self._stop_event.is_set():
            if not self.state.ws.ready.is_set() or not self._resume.is_set():
                run_coroutine_threadsafe(self.state.ws.speaking(False), self.loop)
                logger.debug("Voice playback has been suspended!")

                wait_for = []

                if not self.state.ws.ready.is_set():
                    wait_for.append(self.state.ws.ready)
                if not self._resume.is_set():
                    wait_for.append(self._resume)

                with self._cond:
                    while not (self._stop_event.is_set() or all(x.is_set() for x in wait_for)):
                        self._cond.wait()
                if self._stop_event.is_set():
                    continue

                run_coroutine_threadsafe(self.state.ws.speaking(), self.loop)
                logger.debug("Voice playback has been resumed!")
                start = None
                loops = 0

            if data := self.current_audio.read(self._encoder.frame_size):
                self.state.ws.send_packet(data, self._encoder, needs_encode=self.current_audio.needs_encode)
            else:
                if self.current_audio.locked_stream or not self.current_audio.audio_complete:
                    # if more audio is expected
                    self.state.ws.send_packet(b"\xF8\xFF\xFE", self._encoder, needs_encode=False)
                else:
                    break

            if not start:
                start = perf_counter()

            loops += 1
            self._sent_payloads += 1  # used for duration calc
            sleep(max(0.0, start + (self._encoder.delay * loops) - perf_counter()))
    finally:
        asyncio.run_coroutine_threadsafe(self.state.ws.speaking(False), self.loop)
        self.current_audio.cleanup()
        self.loop.call_soon_threadsafe(self._stopped.set)

Errors

NaffException

Bases: Exception

Base Exception of Naff.

Source code in naff/client/errors.py
52
53
class NaffException(Exception):
    """Base Exception of Naff."""

BotException

Bases: NaffException

An issue occurred in the client, likely user error.

Source code in naff/client/errors.py
56
57
class BotException(NaffException):
    """An issue occurred in the client, likely user error."""

GatewayNotFound

Bases: NaffException

An exception that is raised when the gateway for Discord could not be found.

Source code in naff/client/errors.py
60
61
62
63
64
class GatewayNotFound(NaffException):
    """An exception that is raised when the gateway for Discord could not be found."""

    def __init__(self) -> None:
        super().__init__("Unable to find discord gateway!")

LoginError

Bases: BotException

The bot failed to login, check your token.

Source code in naff/client/errors.py
67
68
class LoginError(BotException):
    """The bot failed to login, check your token."""

HTTPException

Bases: NaffException

A HTTP request resulted in an exception.

Attributes:

Name Type Description
response aiohttp.ClientResponse

The response of the HTTP request

text str

The text of the exception, could be None

status int

The HTTP status code

code int

The discord error code, if one is provided

route Route

The HTTP route that was used

Source code in naff/client/errors.py
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
class HTTPException(NaffException):
    """
    A HTTP request resulted in an exception.

    Attributes:
        response aiohttp.ClientResponse: The response of the HTTP request
        text str: The text of the exception, could be None
        status int: The HTTP status code
        code int: The discord error code, if one is provided
        route Route: The HTTP route that was used

    """

    def __init__(
        self,
        response: aiohttp.ClientResponse,
        text: const.Absent[str] = const.MISSING,
        discord_code: const.Absent[int] = const.MISSING,
        **kwargs,
    ) -> None:
        self.response: aiohttp.ClientResponse = response
        self.status: int = response.status
        self.code: const.Absent[int] = discord_code
        self.text: const.Absent[str] = text
        self.errors: const.Absent[Any] = const.MISSING
        self.route = kwargs.get("route", const.MISSING)

        if data := kwargs.get("response_data"):
            if isinstance(data, dict):
                self.text = data.get("message", const.MISSING)
                self.code = data.get("code", const.MISSING)
                self.errors = data.get("errors", const.MISSING)
            else:
                self.text = data
        super().__init__(f"{self.status}|{self.response.reason}: {f'({self.code}) ' if self.code else ''}{self.text}")

    def __str__(self) -> str:
        if self.errors:
            try:
                errors = self.search_for_message(self.errors)
            except (KeyError, ValueError, TypeError):
                errors = [self.text]
            out = f"HTTPException: {self.status}|{self.response.reason}: " + "\n".join(errors)
        else:
            out = f"HTTPException: {self.status}|{self.response.reason} || {self.text}"
        return out

    @staticmethod
    def search_for_message(errors: dict, lookup: Optional[dict] = None) -> list[str]:
        """
        Search the exceptions error dictionary for a message explaining the issue.

        Args:
            errors: The error dictionary of the http exception
            lookup: A lookup dictionary to use to convert indexes into named items

        Returns:
            A list of parsed error strings found

        """
        messages: List[str] = []
        errors = errors.get("errors", errors)

        def maybe_int(x: SupportsInt | Any) -> Union[int, Any]:
            """If something can be an integer, convert it to one, otherwise return its normal value"""
            try:
                return int(x)
            except ValueError:
                return x

        def _parse(_errors: dict, keys: Optional[List[str]] = None) -> None:
            """Search through the entire dictionary for any errors defined"""
            for key, val in _errors.items():
                if key == "_errors":
                    key_out = []
                    if keys:
                        if lookup:
                            # this code simply substitutes keys for attribute names
                            _lookup = lookup
                            for _key in keys:
                                _lookup = _lookup[maybe_int(_key)]

                                if isinstance(_lookup, dict):
                                    key_out.append(_lookup.get("name", _key))
                                else:
                                    key_out.append(_key)
                        else:
                            key_out = keys

                    for msg in val:
                        messages.append(f"{'->'.join(key_out)} {msg['code']}: {msg['message']}")
                else:
                    if keys:
                        keys.append(key)
                    else:
                        keys = [key]
                    _parse(val, keys)

        _parse(errors)

        return messages

search_for_message(errors, lookup=None) staticmethod

Search the exceptions error dictionary for a message explaining the issue.

Parameters:

Name Type Description Default
errors dict

The error dictionary of the http exception

required
lookup Optional[dict]

A lookup dictionary to use to convert indexes into named items

None

Returns:

Type Description
list[str]

A list of parsed error strings found

Source code in naff/client/errors.py
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
@staticmethod
def search_for_message(errors: dict, lookup: Optional[dict] = None) -> list[str]:
    """
    Search the exceptions error dictionary for a message explaining the issue.

    Args:
        errors: The error dictionary of the http exception
        lookup: A lookup dictionary to use to convert indexes into named items

    Returns:
        A list of parsed error strings found

    """
    messages: List[str] = []
    errors = errors.get("errors", errors)

    def maybe_int(x: SupportsInt | Any) -> Union[int, Any]:
        """If something can be an integer, convert it to one, otherwise return its normal value"""
        try:
            return int(x)
        except ValueError:
            return x

    def _parse(_errors: dict, keys: Optional[List[str]] = None) -> None:
        """Search through the entire dictionary for any errors defined"""
        for key, val in _errors.items():
            if key == "_errors":
                key_out = []
                if keys:
                    if lookup:
                        # this code simply substitutes keys for attribute names
                        _lookup = lookup
                        for _key in keys:
                            _lookup = _lookup[maybe_int(_key)]

                            if isinstance(_lookup, dict):
                                key_out.append(_lookup.get("name", _key))
                            else:
                                key_out.append(_key)
                    else:
                        key_out = keys

                for msg in val:
                    messages.append(f"{'->'.join(key_out)} {msg['code']}: {msg['message']}")
            else:
                if keys:
                    keys.append(key)
                else:
                    keys = [key]
                _parse(val, keys)

    _parse(errors)

    return messages

DiscordError

Bases: HTTPException

A discord-side error.

Source code in naff/client/errors.py
174
175
class DiscordError(HTTPException):
    """A discord-side error."""

BadRequest

Bases: HTTPException

A bad request was made.

Source code in naff/client/errors.py
178
179
class BadRequest(HTTPException):
    """A bad request was made."""

Forbidden

Bases: HTTPException

You do not have access to this.

Source code in naff/client/errors.py
182
183
class Forbidden(HTTPException):
    """You do not have access to this."""

NotFound

Bases: HTTPException

This resource could not be found.

Source code in naff/client/errors.py
186
187
class NotFound(HTTPException):
    """This resource could not be found."""

RateLimited

Bases: HTTPException

Discord is rate limiting this application.

Source code in naff/client/errors.py
190
191
class RateLimited(HTTPException):
    """Discord is rate limiting this application."""

TooManyChanges

Bases: NaffException

You have changed something too frequently.

Source code in naff/client/errors.py
194
195
class TooManyChanges(NaffException):
    """You have changed something too frequently."""

WebSocketClosed

Bases: NaffException

The websocket was closed.

Source code in naff/client/errors.py
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
class WebSocketClosed(NaffException):
    """The websocket was closed."""

    code: int = 0
    codes: Dict[int, str] = {
        1000: "Normal Closure",
        4000: "Unknown Error",
        4001: "Unknown OpCode",
        4002: "Decode Error",
        4003: "Not Authenticated",
        4004: "Authentication Failed",
        4005: "Already Authenticated",
        4007: "Invalid seq",
        4008: "Rate limited",
        4009: "Session Timed Out",
        4010: "Invalid Shard",
        4011: "Sharding Required",
        4012: "Invalid API Version",
        4013: "Invalid Intents",
        4014: "Disallowed Intents",
    }

    def __init__(self, code: int) -> None:
        self.code = code
        super().__init__(f"The Websocket closed with code: {code} - {self.codes.get(code, 'Unknown Error')}")

VoiceWebSocketClosed

Bases: NaffException

The voice websocket was closed.

Source code in naff/client/errors.py
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
class VoiceWebSocketClosed(NaffException):
    """The voice websocket was closed."""

    code: int = 0
    codes: Dict[int, str] = {
        1000: "Normal Closure",
        4000: "Unknown Error",
        4001: "Unknown OpCode",
        4002: "Decode Error",
        4003: "Not Authenticated",
        4004: "Authentication Failed",
        4005: "Already Authenticated",
        4006: "Session no longer valid",
        4007: "Invalid seq",
        4009: "Session Timed Out",
        4011: "Server not found",
        4012: "Unknown protocol",
        4014: "Disconnected",
        4015: "Voice Server Crashed",
        4016: "Unknown encryption mode",
    }

    def __init__(self, code: int) -> None:
        self.code = code
        super().__init__(f"The Websocket closed with code: {code} - {self.codes.get(code, 'Unknown Error')}")

WebSocketRestart

Bases: NaffException

The websocket closed, and is safe to restart.

Source code in naff/client/errors.py
252
253
254
255
256
257
258
259
class WebSocketRestart(NaffException):
    """The websocket closed, and is safe to restart."""

    resume: bool = False

    def __init__(self, resume: bool = False) -> None:
        self.resume = resume
        super().__init__("Websocket connection closed... reconnecting")

ExtensionException

Bases: BotException

An error occurred with an extension.

Source code in naff/client/errors.py
262
263
class ExtensionException(BotException):
    """An error occurred with an extension."""

ExtensionNotFound

Bases: ExtensionException

The desired extension was not found.

Source code in naff/client/errors.py
266
267
class ExtensionNotFound(ExtensionException):
    """The desired extension was not found."""

ExtensionLoadException

Bases: ExtensionException

An error occurred loading an extension.

Source code in naff/client/errors.py
270
271
class ExtensionLoadException(ExtensionException):
    """An error occurred loading an extension."""

CommandException

Bases: BotException

An error occurred trying to execute a command.

Source code in naff/client/errors.py
274
275
class CommandException(BotException):
    """An error occurred trying to execute a command."""

CommandOnCooldown

Bases: CommandException

A command is on cooldown, and was attempted to be executed.

Attributes:

Name Type Description
command BaseCommand

The command that is on cooldown

cooldown CooldownSystem

The cooldown system

Source code in naff/client/errors.py
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
class CommandOnCooldown(CommandException):
    """
    A command is on cooldown, and was attempted to be executed.

    Attributes:
        command BaseCommand: The command that is on cooldown
        cooldown CooldownSystem: The cooldown system

    """

    def __init__(self, command: "BaseCommand", cooldown: "CooldownSystem") -> None:
        self.command: "BaseCommand" = command
        self.cooldown: "CooldownSystem" = cooldown

        super().__init__(f"Command on cooldown... {cooldown.get_cooldown_time():.2f} seconds until reset")

MaxConcurrencyReached

Bases: CommandException

A command has exhausted the max concurrent requests.

Source code in naff/client/errors.py
295
296
297
298
299
300
301
302
class MaxConcurrencyReached(CommandException):
    """A command has exhausted the max concurrent requests."""

    def __init__(self, command: "BaseCommand", max_conc: "MaxConcurrency") -> None:
        self.command: "BaseCommand" = command
        self.max_conc: "MaxConcurrency" = max_conc

        super().__init__(f"Command has exhausted the max concurrent requests. ({max_conc.concurrent} simultaneously)")

CommandCheckFailure

Bases: CommandException

A command check failed.

Attributes:

Name Type Description
command BaseCommand

The command that's check failed

check Callable[..., Coroutine]

The check that failed

Source code in naff/client/errors.py
305
306
307
308
309
310
311
312
313
314
315
316
317
318
class CommandCheckFailure(CommandException):
    """
    A command check failed.

    Attributes:
        command BaseCommand: The command that's check failed
        check Callable[..., Coroutine]: The check that failed

    """

    def __init__(self, command: "BaseCommand", check: Callable[..., Coroutine], context: "Context") -> None:
        self.command: "BaseCommand" = command
        self.check: Callable[..., Coroutine] = check
        self.context = context

BadArgument

Bases: CommandException

A prefixed command encountered an invalid argument.

Source code in naff/client/errors.py
321
322
323
324
325
326
327
328
329
class BadArgument(CommandException):
    """A prefixed command encountered an invalid argument."""

    def __init__(self, message: Optional[str] = None, *args: Any) -> None:
        if message is not None:
            message = escape_mentions(message)
            super().__init__(message, *args)
        else:
            super().__init__(*args)

MessageException

Bases: BotException

A message operation encountered an exception.

Source code in naff/client/errors.py
332
333
class MessageException(BotException):
    """A message operation encountered an exception."""

EmptyMessageException

Bases: MessageException

You have attempted to send a message without any content or embeds

Source code in naff/client/errors.py
336
337
class EmptyMessageException(MessageException):
    """You have attempted to send a message without any content or embeds"""

EphemeralEditException

Bases: MessageException

Your bot attempted to edit an ephemeral message. This is not possible.

Its worth noting you can edit an ephemeral message with component's edit_origin method.

Source code in naff/client/errors.py
340
341
342
343
344
345
346
347
348
349
350
class EphemeralEditException(MessageException):
    """
    Your bot attempted to edit an ephemeral message. This is not possible.

    Its worth noting you can edit an ephemeral message with component's
    `edit_origin` method.

    """

    def __init__(self) -> None:
        super().__init__("Ephemeral messages cannot be edited.")

ThreadException

Bases: BotException

A thread operation encountered an exception.

Source code in naff/client/errors.py
353
354
class ThreadException(BotException):
    """A thread operation encountered an exception."""

ThreadOutsideOfGuild

Bases: ThreadException

A thread was attempted to be created outside of a guild.

Source code in naff/client/errors.py
357
358
359
360
361
class ThreadOutsideOfGuild(ThreadException):
    """A thread was attempted to be created outside of a guild."""

    def __init__(self) -> None:
        super().__init__("Threads cannot be created outside of guilds")

InteractionException

Bases: BotException

An error occurred with an interaction.

Source code in naff/client/errors.py
364
365
class InteractionException(BotException):
    """An error occurred with an interaction."""

InteractionMissingAccess

Bases: InteractionException

The bot does not have access to the specified scope.

Source code in naff/client/errors.py
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
class InteractionMissingAccess(InteractionException):
    """The bot does not have access to the specified scope."""

    def __init__(self, scope: "Snowflake_Type") -> None:
        self.scope: "Snowflake_Type" = scope

        if scope == const.GLOBAL_SCOPE:
            err_msg = "Unable to sync commands global commands"
        else:
            err_msg = (
                f"Unable to sync commands for guild `{scope}` -- Ensure the bot properly added to that guild "
                f"with `application.commands` scope. "
            )

        super().__init__(err_msg)

AlreadyDeferred

Bases: BotException

An interaction was already deferred, and you attempted to defer it again.

Source code in naff/client/errors.py
385
386
class AlreadyDeferred(BotException):
    """An interaction was already deferred, and you attempted to defer it again."""

ForeignWebhookException

Bases: NaffException

Raised when you attempt to send using a webhook you did not create.

Source code in naff/client/errors.py
389
390
class ForeignWebhookException(NaffException):
    """Raised when you attempt to send using a webhook you did not create."""

EventLocationNotProvided

Bases: BotException

Raised when you have entity_type external and no location is provided.

Source code in naff/client/errors.py
393
394
class EventLocationNotProvided(BotException):
    """Raised when you have entity_type external and no location is provided."""

VoiceAlreadyConnected

Bases: BotException

Raised when you attempt to connect a voice channel that is already connected.

Source code in naff/client/errors.py
397
398
399
400
401
class VoiceAlreadyConnected(BotException):
    """Raised when you attempt to connect a voice channel that is already connected."""

    def __init__(self) -> None:
        super().__init__("Bot already connected to the voice channel")

VoiceNotConnected

Bases: BotException

Raised when you attempt to connect a voice channel that is not connected.

Source code in naff/client/errors.py
404
405
406
407
408
class VoiceNotConnected(BotException):
    """Raised when you attempt to connect a voice channel that is not connected."""

    def __init__(self) -> None:
        super().__init__("Bot is not connected to any voice channels in given guild")

VoiceConnectionTimeout

Bases: NaffException

Raised when the bot fails to connect to a voice channel.

Source code in naff/client/errors.py
411
412
413
414
415
class VoiceConnectionTimeout(NaffException):
    """Raised when the bot fails to connect to a voice channel."""

    def __init__(self) -> None:
        super().__init__("Failed to connect to voice channel. Did not receive a response from Discord")

Events

These are events dispatched by Discord. This is intended as a reference so you know what data to expect for each event.

Example Usage:

The event classes outlined here are in CamelCase to comply with Class naming convention, however the event names are actually in lower_case_with_underscores so your listeners should be named as following:

1
2
3
4
5
6
7
8
9
@listen()
def on_ready():
    # ready events pass no data, so dont have params
    print("Im ready!")

@listen()
def on_guild_join(event):
    # guild_create events pass a guild object, expect a single param
    print(f"{event.guild.name} created")

Warning

While all of these events are documented, not all of them are used, currently.

RawGatewayEvent

Bases: BaseEvent

An event dispatched from the gateway.

Holds the raw dict that the gateway dispatches

Source code in naff/api/events/discord.py
104
105
106
107
108
109
110
111
112
113
114
@define(kw_only=False)
class RawGatewayEvent(BaseEvent):
    """
    An event dispatched from the gateway.

    Holds the raw dict that the gateway dispatches

    """

    data: dict = field(factory=dict)
    """Raw Data from the gateway"""

data: dict = field(factory=dict) class-attribute

Raw Data from the gateway

AutoModExec

Bases: BaseEvent

Dispatched when an auto modation action is executed

Source code in naff/api/events/discord.py
117
118
119
120
121
122
123
@define(kw_only=False)
class AutoModExec(BaseEvent):
    """Dispatched when an auto modation action is executed"""

    execution: "AutoModerationAction" = field(metadata=docs("The executed auto mod action"))
    channel: "BaseChannel" = field(metadata=docs("The channel the action was executed in"))
    guild: "Guild" = field(metadata=docs("The guild the action was executed in"))

AutoModUpdated

Bases: AutoModCreated

Dispatched when an auto mod rule is modified

Source code in naff/api/events/discord.py
132
133
134
135
136
@define(kw_only=False)
class AutoModUpdated(AutoModCreated):
    """Dispatched when an auto mod rule is modified"""

    ...

AutoModDeleted

Bases: AutoModCreated

Dispatched when an auto mod rule is deleted

Source code in naff/api/events/discord.py
139
140
141
142
143
@define(kw_only=False)
class AutoModDeleted(AutoModCreated):
    """Dispatched when an auto mod rule is deleted"""

    ...

ChannelCreate

Bases: BaseEvent

Dispatched when a channel is created.

Source code in naff/api/events/discord.py
146
147
148
149
150
@define(kw_only=False)
class ChannelCreate(BaseEvent):
    """Dispatched when a channel is created."""

    channel: "BaseChannel" = field(metadata=docs("The channel this event is dispatched from"))

ChannelUpdate

Bases: BaseEvent

Dispatched when a channel is updated.

Source code in naff/api/events/discord.py
153
154
155
156
157
158
159
160
@define(kw_only=False)
class ChannelUpdate(BaseEvent):
    """Dispatched when a channel is updated."""

    before: "BaseChannel" = field()
    """Channel before this event. MISSING if it was not cached before"""
    after: "BaseChannel" = field()
    """Channel after this event"""

before: BaseChannel = field() class-attribute

Channel before this event. MISSING if it was not cached before

after: BaseChannel = field() class-attribute

Channel after this event

ChannelDelete

Bases: ChannelCreate

Dispatched when a channel is deleted.

Source code in naff/api/events/discord.py
163
164
165
@define(kw_only=False)
class ChannelDelete(ChannelCreate):
    """Dispatched when a channel is deleted."""

ChannelPinsUpdate

Bases: ChannelCreate

Dispatched when a channel's pins are updated.

Source code in naff/api/events/discord.py
168
169
170
171
172
173
@define(kw_only=False)
class ChannelPinsUpdate(ChannelCreate):
    """Dispatched when a channel's pins are updated."""

    last_pin_timestamp: "Timestamp" = field()
    """The time at which the most recent pinned message was pinned"""

last_pin_timestamp: Timestamp = field() class-attribute

The time at which the most recent pinned message was pinned

ThreadCreate

Bases: BaseEvent

Dispatched when a thread is created.

Source code in naff/api/events/discord.py
176
177
178
179
180
@define(kw_only=False)
class ThreadCreate(BaseEvent):
    """Dispatched when a thread is created."""

    thread: "TYPE_THREAD_CHANNEL" = field(metadata=docs("The thread this event is dispatched from"))

ThreadUpdate

Bases: ThreadCreate

Dispatched when a thread is updated.

Source code in naff/api/events/discord.py
183
184
185
@define(kw_only=False)
class ThreadUpdate(ThreadCreate):
    """Dispatched when a thread is updated."""

ThreadDelete

Bases: ThreadCreate

Dispatched when a thread is deleted.

Source code in naff/api/events/discord.py
188
189
190
@define(kw_only=False)
class ThreadDelete(ThreadCreate):
    """Dispatched when a thread is deleted."""

ThreadListSync

Bases: BaseEvent

Dispatched when gaining access to a channel, contains all active threads in that channel.

Source code in naff/api/events/discord.py
193
194
195
196
197
198
199
200
201
202
@define(kw_only=False)
class ThreadListSync(BaseEvent):
    """Dispatched when gaining access to a channel, contains all active threads in that channel."""

    channel_ids: Sequence["Snowflake_Type"] = field()
    """The parent channel ids whose threads are being synced. If omitted, then threads were synced for the entire guild. This array may contain channel_ids that have no active threads as well, so you know to clear that data."""
    threads: List["BaseChannel"] = field()
    """all active threads in the given channels that the current user can access"""
    members: List["Member"] = field()
    """all thread member objects from the synced threads for the current user, indicating which threads the current user has been added to"""

channel_ids: Sequence[Snowflake_Type] = field() class-attribute

The parent channel ids whose threads are being synced. If omitted, then threads were synced for the entire guild. This array may contain channel_ids that have no active threads as well, so you know to clear that data.

threads: List[BaseChannel] = field() class-attribute

all active threads in the given channels that the current user can access

members: List[Member] = field() class-attribute

all thread member objects from the synced threads for the current user, indicating which threads the current user has been added to

ThreadMemberUpdate

Bases: ThreadCreate

Dispatched when the thread member object for the current user is updated.

??? info "Note from Discord" This event is documented for completeness, but unlikely to be used by most bots. For bots, this event largely is just a signal that you are a member of the thread

Source code in naff/api/events/discord.py
206
207
208
209
210
211
212
213
214
215
216
217
218
@define(kw_only=False)
class ThreadMemberUpdate(ThreadCreate):
    """
    Dispatched when the thread member object for the current user is updated.

    ??? info "Note from Discord"     This event is documented for
    completeness, but unlikely to be used by most bots. For bots, this
    event largely is just a signal that you are a member of the thread

    """

    member: "Member" = field()
    """The member who was added"""

member: Member = field() class-attribute

The member who was added

ThreadMembersUpdate

Bases: BaseEvent

Dispatched when anyone is added or removed from a thread.

Source code in naff/api/events/discord.py
221
222
223
224
225
226
227
228
229
230
231
232
@define(kw_only=False)
class ThreadMembersUpdate(BaseEvent):
    """Dispatched when anyone is added or removed from a thread."""

    id: "Snowflake_Type" = field()
    """The ID of the thread"""
    member_count: int = field(default=50)
    """the approximate number of members in the thread, capped at 50"""
    added_members: List["Member"] = field(factory=list)
    """Users added to the thread"""
    removed_member_ids: List["Snowflake_Type"] = field(factory=list)
    """Users removed from the thread"""

id: Snowflake_Type = field() class-attribute

The ID of the thread

member_count: int = field(default=50) class-attribute

the approximate number of members in the thread, capped at 50

added_members: List[Member] = field(factory=list) class-attribute

Users added to the thread

removed_member_ids: List[Snowflake_Type] = field(factory=list) class-attribute

Users removed from the thread

GuildJoin

Bases: BaseEvent

Dispatched when a guild is joined, created, or becomes available.

Note

This is called multiple times during startup, check the bot is ready before responding to this.

Source code in naff/api/events/discord.py
235
236
237
238
239
240
241
242
243
244
245
246
@define(kw_only=False)
class GuildJoin(BaseEvent):
    """
    Dispatched when a guild is joined, created, or becomes available.

    !!! note
        This is called multiple times during startup, check the bot is ready before responding to this.

    """

    guild: "Guild" = field()
    """The guild that was created"""

guild: Guild = field() class-attribute

The guild that was created

GuildUpdate

Bases: BaseEvent

Dispatched when a guild is updated.

Source code in naff/api/events/discord.py
249
250
251
252
253
254
255
256
@define(kw_only=False)
class GuildUpdate(BaseEvent):
    """Dispatched when a guild is updated."""

    before: "Guild" = field()
    """Guild before this event"""
    after: "Guild" = field()
    """Guild after this event"""

before: Guild = field() class-attribute

Guild before this event

after: Guild = field() class-attribute

Guild after this event

GuildLeft

Bases: BaseEvent, GuildEvent

Dispatched when a guild is left.

Source code in naff/api/events/discord.py
259
260
261
262
263
264
@define(kw_only=False)
class GuildLeft(BaseEvent, GuildEvent):
    """Dispatched when a guild is left."""

    guild: Optional["Guild"] = field(default=MISSING)
    """The guild, if it was cached"""

guild: Optional[Guild] = field(default=MISSING) class-attribute

The guild, if it was cached

GuildUnavailable

Bases: BaseEvent, GuildEvent

Dispatched when a guild is not available.

Source code in naff/api/events/discord.py
267
268
269
270
271
272
@define(kw_only=False)
class GuildUnavailable(BaseEvent, GuildEvent):
    """Dispatched when a guild is not available."""

    guild: Optional["Guild"] = field(default=MISSING)
    """The guild, if it was cached"""

guild: Optional[Guild] = field(default=MISSING) class-attribute

The guild, if it was cached

BanCreate

Bases: BaseEvent, GuildEvent

Dispatched when someone was banned from a guild.

Source code in naff/api/events/discord.py
275
276
277
278
279
@define(kw_only=False)
class BanCreate(BaseEvent, GuildEvent):
    """Dispatched when someone was banned from a guild."""

    user: "BaseUser" = field(metadata=docs("The user"))

BanRemove

Bases: BanCreate

Dispatched when a users ban is removed.

Source code in naff/api/events/discord.py
282
283
284
@define(kw_only=False)
class BanRemove(BanCreate):
    """Dispatched when a users ban is removed."""

GuildEmojisUpdate

Bases: BaseEvent, GuildEvent

Dispatched when a guild's emojis are updated.

Source code in naff/api/events/discord.py
287
288
289
290
291
292
293
294
@define(kw_only=False)
class GuildEmojisUpdate(BaseEvent, GuildEvent):
    """Dispatched when a guild's emojis are updated."""

    before: List["CustomEmoji"] = field(factory=list)
    """List of emoji before this event. Only includes emojis that were cached. To enable the emoji cache (and this field), start your bot with `Client(enable_emoji_cache=True)`"""
    after: List["CustomEmoji"] = field(factory=list)
    """List of emoji after this event"""

before: List[CustomEmoji] = field(factory=list) class-attribute

List of emoji before this event. Only includes emojis that were cached. To enable the emoji cache (and this field), start your bot with Client(enable_emoji_cache=True)

after: List[CustomEmoji] = field(factory=list) class-attribute

List of emoji after this event

GuildStickersUpdate

Bases: BaseEvent, GuildEvent

Dispatched when a guild's stickers are updated.

Source code in naff/api/events/discord.py
297
298
299
300
301
302
@define(kw_only=False)
class GuildStickersUpdate(BaseEvent, GuildEvent):
    """Dispatched when a guild's stickers are updated."""

    stickers: List["Sticker"] = field(factory=list)
    """List of stickers from after this event"""

stickers: List[Sticker] = field(factory=list) class-attribute

List of stickers from after this event

MemberAdd

Bases: BaseEvent, GuildEvent

Dispatched when a member is added to a guild.

Source code in naff/api/events/discord.py
305
306
307
308
309
@define(kw_only=False)
class MemberAdd(BaseEvent, GuildEvent):
    """Dispatched when a member is added to a guild."""

    member: "Member" = field(metadata=docs("The member who was added"))

MemberRemove

Bases: MemberAdd

Dispatched when a member is removed from a guild.

Source code in naff/api/events/discord.py
312
313
314
315
316
317
318
@define(kw_only=False)
class MemberRemove(MemberAdd):
    """Dispatched when a member is removed from a guild."""

    member: Union["Member", "User"] = field(
        metadata=docs("The member who was added, can be user if the member is not cached")
    )

MemberUpdate

Bases: BaseEvent, GuildEvent

Dispatched when a member is updated.

Source code in naff/api/events/discord.py
321
322
323
324
325
326
327
328
@define(kw_only=False)
class MemberUpdate(BaseEvent, GuildEvent):
    """Dispatched when a member is updated."""

    before: "Member" = field()
    """The state of the member before this event"""
    after: "Member" = field()
    """The state of the member after this event"""

before: Member = field() class-attribute

The state of the member before this event

after: Member = field() class-attribute

The state of the member after this event

RoleCreate

Bases: BaseEvent, GuildEvent

Dispatched when a role is created.

Source code in naff/api/events/discord.py
331
332
333
334
335
336
@define(kw_only=False)
class RoleCreate(BaseEvent, GuildEvent):
    """Dispatched when a role is created."""

    role: "Role" = field()
    """The created role"""

role: Role = field() class-attribute

The created role

RoleUpdate

Bases: BaseEvent, GuildEvent

Dispatched when a role is updated.

Source code in naff/api/events/discord.py
339
340
341
342
343
344
345
346
@define(kw_only=False)
class RoleUpdate(BaseEvent, GuildEvent):
    """Dispatched when a role is updated."""

    before: Absent["Role"] = field()
    """The role before this event"""
    after: "Role" = field()
    """The role after this event"""

before: Absent[Role] = field() class-attribute

The role before this event

after: Role = field() class-attribute

The role after this event

RoleDelete

Bases: BaseEvent, GuildEvent

Dispatched when a guild role is deleted.

Source code in naff/api/events/discord.py
349
350
351
352
353
354
355
356
@define(kw_only=False)
class RoleDelete(BaseEvent, GuildEvent):
    """Dispatched when a guild role is deleted."""

    id: "Snowflake_Type" = field()
    """The ID of the deleted role"""
    role: Absent["Role"] = field()
    """The deleted role"""

id: Snowflake_Type = field() class-attribute

The ID of the deleted role

role: Absent[Role] = field() class-attribute

The deleted role

GuildMembersChunk

Bases: BaseEvent, GuildEvent

Sent in response to Guild Request Members.

You can use the chunk_index and chunk_count to calculate how many chunks are left for your request.

Source code in naff/api/events/discord.py
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
@define(kw_only=False)
class GuildMembersChunk(BaseEvent, GuildEvent):
    """
    Sent in response to Guild Request Members.

    You can use the `chunk_index` and `chunk_count` to calculate how
    many chunks are left for your request.

    """

    chunk_index: int = field()
    """The chunk index in the expected chunks for this response (0 <= chunk_index < chunk_count)"""
    chunk_count: int = field()
    """the total number of expected chunks for this response"""
    presences: List = field()
    """if passing true to `REQUEST_GUILD_MEMBERS`, presences of the returned members will be here"""
    nonce: str = field()
    """The nonce used in the request, if any"""
    members: List["Member"] = field(factory=list)
    """A list of members"""

chunk_index: int = field() class-attribute

The chunk index in the expected chunks for this response (0 <= chunk_index < chunk_count)

chunk_count: int = field() class-attribute

the total number of expected chunks for this response

presences: List = field() class-attribute

if passing true to REQUEST_GUILD_MEMBERS, presences of the returned members will be here

nonce: str = field() class-attribute

The nonce used in the request, if any

members: List[Member] = field(factory=list) class-attribute

A list of members

IntegrationCreate

Bases: BaseEvent

Dispatched when a guild integration is created.

Source code in naff/api/events/discord.py
381
382
383
384
385
@define(kw_only=False)
class IntegrationCreate(BaseEvent):
    """Dispatched when a guild integration is created."""

    integration: "GuildIntegration" = field()

IntegrationUpdate

Bases: IntegrationCreate

Dispatched when a guild integration is updated.

Source code in naff/api/events/discord.py
388
389
390
@define(kw_only=False)
class IntegrationUpdate(IntegrationCreate):
    """Dispatched when a guild integration is updated."""

IntegrationDelete

Bases: BaseEvent, GuildEvent

Dispatched when a guild integration is deleted.

Source code in naff/api/events/discord.py
393
394
395
396
397
398
399
400
@define(kw_only=False)
class IntegrationDelete(BaseEvent, GuildEvent):
    """Dispatched when a guild integration is deleted."""

    id: "Snowflake_Type" = field()
    """The ID of the integration"""
    application_id: "Snowflake_Type" = field(default=None)
    """The ID of the bot/application for this integration"""

id: Snowflake_Type = field() class-attribute

The ID of the integration

application_id: Snowflake_Type = field(default=None) class-attribute

The ID of the bot/application for this integration

InviteCreate

Bases: BaseEvent

Dispatched when a guild invite is created.

Source code in naff/api/events/discord.py
403
404
405
406
407
@define(kw_only=False)
class InviteCreate(BaseEvent):
    """Dispatched when a guild invite is created."""

    invite: naff.models.Invite = field()

InviteDelete

Bases: InviteCreate

Dispatched when an invite is deleted.

Source code in naff/api/events/discord.py
410
411
412
@define(kw_only=False)
class InviteDelete(InviteCreate):
    """Dispatched when an invite is deleted."""

MessageCreate

Bases: BaseEvent

Dispatched when a message is created.

Source code in naff/api/events/discord.py
415
416
417
418
419
@define(kw_only=False)
class MessageCreate(BaseEvent):
    """Dispatched when a message is created."""

    message: "Message" = field()

MessageUpdate

Bases: BaseEvent

Dispatched when a message is edited.

Source code in naff/api/events/discord.py
422
423
424
425
426
427
428
429
@define(kw_only=False)
class MessageUpdate(BaseEvent):
    """Dispatched when a message is edited."""

    before: "Message" = field()
    """The message before this event was created"""
    after: "Message" = field()
    """The message after this event was created"""

before: Message = field() class-attribute

The message before this event was created

after: Message = field() class-attribute

The message after this event was created

MessageDelete

Bases: BaseEvent

Dispatched when a message is deleted.

Source code in naff/api/events/discord.py
432
433
434
435
436
@define(kw_only=False)
class MessageDelete(BaseEvent):
    """Dispatched when a message is deleted."""

    message: "Message" = field()

MessageDeleteBulk

Bases: BaseEvent, GuildEvent

Dispatched when multiple messages are deleted at once.

Source code in naff/api/events/discord.py
439
440
441
442
443
444
445
446
@define(kw_only=False)
class MessageDeleteBulk(BaseEvent, GuildEvent):
    """Dispatched when multiple messages are deleted at once."""

    channel_id: "Snowflake_Type" = field()
    """The ID of the channel these were deleted in"""
    ids: List["Snowflake_Type"] = field(factory=list)
    """A list of message snowflakes"""

channel_id: Snowflake_Type = field() class-attribute

The ID of the channel these were deleted in

ids: List[Snowflake_Type] = field(factory=list) class-attribute

A list of message snowflakes

MessageReactionAdd

Bases: BaseEvent

Dispatched when a reaction is added to a message.

Source code in naff/api/events/discord.py
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
@define(kw_only=False)
class MessageReactionAdd(BaseEvent):
    """Dispatched when a reaction is added to a message."""

    message: "Message" = field(metadata=docs("The message that was reacted to"))
    emoji: "PartialEmoji" = field(metadata=docs("The emoji that was added to the message"))
    author: Union["Member", "User"] = field(metadata=docs("The user who added the reaction"))
    # reaction can be None when the message is not in the cache, and it was the last reaction, and it was deleted in the event
    reaction: Optional["Reaction"] = field(
        default=None, metadata=docs("The reaction object corresponding to the emoji")
    )

    @property
    def reaction_count(self) -> int:
        """Times the emoji in the event has been used to react"""
        if self.reaction is None:
            return 0
        return self.reaction.count

reaction_count() property

Times the emoji in the event has been used to react

Source code in naff/api/events/discord.py
461
462
463
464
465
466
@property
def reaction_count(self) -> int:
    """Times the emoji in the event has been used to react"""
    if self.reaction is None:
        return 0
    return self.reaction.count

MessageReactionRemove

Bases: MessageReactionAdd

Dispatched when a reaction is removed.

Source code in naff/api/events/discord.py
469
470
471
@define(kw_only=False)
class MessageReactionRemove(MessageReactionAdd):
    """Dispatched when a reaction is removed."""

MessageReactionRemoveAll

Bases: BaseEvent, GuildEvent

Dispatched when all reactions are removed from a message.

Source code in naff/api/events/discord.py
474
475
476
477
478
479
@define(kw_only=False)
class MessageReactionRemoveAll(BaseEvent, GuildEvent):
    """Dispatched when all reactions are removed from a message."""

    message: "Message" = field()
    """The message that was reacted to"""

message: Message = field() class-attribute

The message that was reacted to

PresenceUpdate

Bases: BaseEvent

A user's presence has changed.

Source code in naff/api/events/discord.py
482
483
484
485
486
487
488
489
490
491
492
493
494
495
@define(kw_only=False)
class PresenceUpdate(BaseEvent):
    """A user's presence has changed."""

    user: "User" = field()
    """The user in question"""
    status: str = field()
    """'Either `idle`, `dnd`, `online`, or `offline`'"""
    activities: List["Activity"] = field()
    """The users current activities"""
    client_status: dict = field()
    """What platform the user is reported as being on"""
    guild_id: "Snowflake_Type" = field()
    """The guild this presence update was dispatched from"""

user: User = field() class-attribute

The user in question

status: str = field() class-attribute

'Either idle, dnd, online, or offline'

activities: List[Activity] = field() class-attribute

The users current activities

client_status: dict = field() class-attribute

What platform the user is reported as being on

guild_id: Snowflake_Type = field() class-attribute

The guild this presence update was dispatched from

StageInstanceCreate

Bases: BaseEvent

Dispatched when a stage instance is created.

Source code in naff/api/events/discord.py
498
499
500
501
502
@define(kw_only=False)
class StageInstanceCreate(BaseEvent):
    """Dispatched when a stage instance is created."""

    stage_instance: "StageInstance" = field(metadata=docs("The stage instance"))

StageInstanceDelete

Bases: StageInstanceCreate

Dispatched when a stage instance is deleted.

Source code in naff/api/events/discord.py
505
506
507
@define(kw_only=False)
class StageInstanceDelete(StageInstanceCreate):
    """Dispatched when a stage instance is deleted."""

StageInstanceUpdate

Bases: StageInstanceCreate

Dispatched when a stage instance is updated.

Source code in naff/api/events/discord.py
510
511
512
@define(kw_only=False)
class StageInstanceUpdate(StageInstanceCreate):
    """Dispatched when a stage instance is updated."""

TypingStart

Bases: BaseEvent

Dispatched when a user starts typing.

Source code in naff/api/events/discord.py
515
516
517
518
519
520
521
522
523
524
525
526
@define(kw_only=False)
class TypingStart(BaseEvent):
    """Dispatched when a user starts typing."""

    author: Union["User", "Member"] = field()
    """The user who started typing"""
    channel: "BaseChannel" = field()
    """The channel typing is in"""
    guild: "Guild" = field()
    """The ID of the guild this typing is in"""
    timestamp: "Timestamp" = field()
    """unix time (in seconds) of when the user started typing"""

author: Union[User, Member] = field() class-attribute

The user who started typing

channel: BaseChannel = field() class-attribute

The channel typing is in

guild: Guild = field() class-attribute

The ID of the guild this typing is in

timestamp: Timestamp = field() class-attribute

unix time (in seconds) of when the user started typing

WebhooksUpdate

Bases: BaseEvent, GuildEvent

Dispatched when a guild channel webhook is created, updated, or deleted.

Source code in naff/api/events/discord.py
529
530
531
532
533
534
535
@define(kw_only=False)
class WebhooksUpdate(BaseEvent, GuildEvent):
    """Dispatched when a guild channel webhook is created, updated, or deleted."""

    # Discord doesnt sent the webhook object for this event, for some reason
    channel_id: "Snowflake_Type" = field()
    """The ID of the webhook was updated"""

channel_id: Snowflake_Type = field() class-attribute

The ID of the webhook was updated

InteractionCreate

Bases: BaseEvent

Dispatched when a user uses an Application Command.

Source code in naff/api/events/discord.py
538
539
540
541
542
@define(kw_only=False)
class InteractionCreate(BaseEvent):
    """Dispatched when a user uses an Application Command."""

    interaction: dict = field()

ModalResponse

Bases: BaseEvent

Dispatched when a modal receives a response

Source code in naff/api/events/discord.py
545
546
547
548
549
550
@define(kw_only=False)
class ModalResponse(BaseEvent):
    """Dispatched when a modal receives a response"""

    context: "ModalContext" = field()
    """The context data of the modal"""

context: ModalContext = field() class-attribute

The context data of the modal

VoiceStateUpdate

Bases: BaseEvent

Dispatched when a user joins/leaves/moves voice channels.

Source code in naff/api/events/discord.py
553
554
555
556
557
558
559
560
@define(kw_only=False)
class VoiceStateUpdate(BaseEvent):
    """Dispatched when a user joins/leaves/moves voice channels."""

    before: Optional["VoiceState"] = field()
    """The voice state before this event was created or None if the user was not in a voice channel"""
    after: Optional["VoiceState"] = field()
    """The voice state after this event was created or None if the user is no longer in a voice channel"""

before: Optional[VoiceState] = field() class-attribute

The voice state before this event was created or None if the user was not in a voice channel

after: Optional[VoiceState] = field() class-attribute

The voice state after this event was created or None if the user is no longer in a voice channel

These are events dispatched by the client. This is intended as a reference so you know what data to expect for each event.

Example Usage:

The event classes outlined here are in CamelCase to comply with Class naming convention, however the event names are actually in lower_case_with_underscores so your listeners should be named as following:

1
2
3
4
5
6
7
8
9
@listen()
def on_ready():
    # ready events pass no data, so dont have params
    print("Im ready!")

@listen()
def on_guild_join(event):
    # guild_create events pass a guild object, expect a single param
    print(f"{event.guild.name} created")

Warning

While all of these events are documented, not all of them are used, currently.

BaseEvent

A base event that all other events inherit from.

Source code in naff/api/events/internal.py
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
@define(slots=False)
class BaseEvent:
    """A base event that all other events inherit from."""

    override_name: str = field(kw_only=True, default=None)
    """Custom name of the event to be used when dispatching."""
    bot: "Client" = field(kw_only=True, default=MISSING)
    """The client instance that dispatched this event."""

    @property
    def resolved_name(self) -> str:
        """The name of the event, defaults to the class name if not overridden."""
        name = self.override_name or self.__class__.__name__
        return _event_reg.sub("_", name).lower()

    @classmethod
    def listen(cls, coro: Callable[..., Coroutine], client: "Client") -> "models.Listener":
        """
        A shortcut for creating a listener for this event

        Args:
            coro: The coroutine to call when the event is triggered.
            client: The client instance to listen to.


        ??? Hint "Example Usage:"
            ```python
            class SomeClass:
                def __init__(self, bot: Client):
                    Ready.listen(self.some_func, bot)

                async def some_func(self, event):
                    print(f"{event.resolved_name} triggered")
            ```
        Returns:
            A listener object.
        """
        listener = models.Listener.create(cls().resolved_name)(coro)
        client.add_listener(listener)
        return listener

override_name: str = field(kw_only=True, default=None) class-attribute

Custom name of the event to be used when dispatching.

bot: Client = field(kw_only=True, default=MISSING) class-attribute

The client instance that dispatched this event.

resolved_name() property

The name of the event, defaults to the class name if not overridden.

Source code in naff/api/events/internal.py
68
69
70
71
72
@property
def resolved_name(self) -> str:
    """The name of the event, defaults to the class name if not overridden."""
    name = self.override_name or self.__class__.__name__
    return _event_reg.sub("_", name).lower()

listen(coro, client) classmethod

A shortcut for creating a listener for this event

Parameters:

Name Type Description Default
coro Callable[..., Coroutine]

The coroutine to call when the event is triggered.

required
client Client

The client instance to listen to.

required
Example Usage:
1
2
3
4
5
6
class SomeClass:
    def __init__(self, bot: Client):
        Ready.listen(self.some_func, bot)

    async def some_func(self, event):
        print(f"{event.resolved_name} triggered")

Returns:

Type Description
Listener

A listener object.

Source code in naff/api/events/internal.py
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
@classmethod
def listen(cls, coro: Callable[..., Coroutine], client: "Client") -> "models.Listener":
    """
    A shortcut for creating a listener for this event

    Args:
        coro: The coroutine to call when the event is triggered.
        client: The client instance to listen to.


    ??? Hint "Example Usage:"
        ```python
        class SomeClass:
            def __init__(self, bot: Client):
                Ready.listen(self.some_func, bot)

            async def some_func(self, event):
                print(f"{event.resolved_name} triggered")
        ```
    Returns:
        A listener object.
    """
    listener = models.Listener.create(cls().resolved_name)(coro)
    client.add_listener(listener)
    return listener

GuildEvent

A base event that adds guild_id.

Source code in naff/api/events/internal.py
101
102
103
104
105
106
107
108
109
110
@define(slots=False, kw_only=False)
class GuildEvent:
    """A base event that adds guild_id."""

    guild_id: "Snowflake_Type" = field(metadata=docs("The ID of the guild"), converter=to_snowflake)

    @property
    def guild(self) -> "Guild":
        """Guild related to event"""
        return self.bot.cache.get_guild(self.guild_id)

guild() property

Guild related to event

Source code in naff/api/events/internal.py
107
108
109
110
@property
def guild(self) -> "Guild":
    """Guild related to event"""
    return self.bot.cache.get_guild(self.guild_id)

Login

Bases: BaseEvent

The bot has just logged in.

Source code in naff/api/events/internal.py
113
114
115
@define(kw_only=False)
class Login(BaseEvent):
    """The bot has just logged in."""

Connect

Bases: BaseEvent

The bot is now connected to the discord Gateway.

Source code in naff/api/events/internal.py
118
119
120
@define(kw_only=False)
class Connect(BaseEvent):
    """The bot is now connected to the discord Gateway."""

Resume

Bases: BaseEvent

The bot has resumed its connection to the discord Gateway.

Source code in naff/api/events/internal.py
123
124
125
@define(kw_only=False)
class Resume(BaseEvent):
    """The bot has resumed its connection to the discord Gateway."""

Disconnect

Bases: BaseEvent

The bot has just disconnected.

Source code in naff/api/events/internal.py
128
129
130
@define(kw_only=False)
class Disconnect(BaseEvent):
    """The bot has just disconnected."""

ShardConnect

Bases: Connect

A shard just connected to the discord Gateway.

Source code in naff/api/events/internal.py
133
134
135
136
137
@define(kw_only=False)
class ShardConnect(Connect):
    """A shard just connected to the discord Gateway."""

    shard_id: int = field(metadata=docs("The ID of the shard"))

ShardDisconnect

Bases: Disconnect

A shard just disconnected.

Source code in naff/api/events/internal.py
140
141
142
143
144
@define(kw_only=False)
class ShardDisconnect(Disconnect):
    """A shard just disconnected."""

    shard_id: int = field(metadata=docs("The ID of the shard"))

Startup

Bases: BaseEvent

The client is now ready for the first time.

Use this for tasks you want to do upon login, instead of ready, as this will only be called once.

Source code in naff/api/events/internal.py
147
148
149
150
151
152
153
154
155
@define(kw_only=False)
class Startup(BaseEvent):
    """
    The client is now ready for the first time.

    Use this for tasks you want to do upon login, instead of ready, as
    this will only be called once.

    """

Ready

Bases: BaseEvent

The client is now ready.

Note

Don't use this event for things that must only happen once, on startup, as this event may be called multiple times. Instead, use the Startup event

Source code in naff/api/events/internal.py
158
159
160
161
162
163
164
165
166
167
@define(kw_only=False)
class Ready(BaseEvent):
    """
    The client is now ready.

    !!! note
        Don't use this event for things that must only happen once, on startup, as this event may be called multiple times.
        Instead, use the `Startup` event

    """

WebsocketReady

Bases: BaseEvent

The gateway has reported that it is ready.

Source code in naff/api/events/internal.py
170
171
172
173
174
@define(kw_only=False)
class WebsocketReady(BaseEvent):
    """The gateway has reported that it is ready."""

    data: dict = field(metadata=docs("The data from the ready event"))

Component

Bases: BaseEvent

Dispatched when a user uses a Component.

Source code in naff/api/events/internal.py
177
178
179
180
181
@define(kw_only=False)
class Component(BaseEvent):
    """Dispatched when a user uses a Component."""

    context: "ComponentContext" = field(metadata=docs("The context of the interaction"))

Button

Bases: Component

Dispatched when a user uses a Button.

Source code in naff/api/events/internal.py
184
185
186
@define(kw_only=False)
class Button(Component):
    """Dispatched when a user uses a Button."""

Select

Bases: Component

Dispatched when a user uses a Select.

Source code in naff/api/events/internal.py
189
190
191
@define(kw_only=False)
class Select(Component):
    """Dispatched when a user uses a Select."""

Error

Bases: BaseEvent

Dispatched when the library encounters an error.

Source code in naff/api/events/internal.py
194
195
196
197
198
199
200
201
202
@define(kw_only=False)
class Error(BaseEvent):
    """Dispatched when the library encounters an error."""

    source: str = field(metadata=docs("The source of the error"))
    error: Exception = field(metadata=docs("The error that was encountered"))
    args: tuple[Any] = field(factory=tuple)
    kwargs: dict[str, Any] = field(factory=dict)
    ctx: Optional["Context"] = field(default=None, metadata=docs("The Context, if one was active"))